In this document we present the joint analysis of the PASS1A metabolomics datasets.
Load all datasets
Load the data from the cloud, including: phenotypic data, metabolomic datasets, and metabolomics dictionary.
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/supervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/unsupervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/gcp_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/association_analysis_methods.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/data_aux_functions.R")
source("~/Desktop/repos/motrpac/tools/prediction_ml_tools.R")
library(randomForest) # for classification tests
# Load the dmaqc data
merged_dmaqc_data = load_from_bucket("merged_dmaqc_data2019-10-15.RData",
"gs://bic_data_analysis/pass1a/pheno_dmaqc/",F)
merged_dmaqc_data = merged_dmaqc_data[[1]]
rownames(merged_dmaqc_data) = as.character(merged_dmaqc_data$vial_label)
# define the tissue variable
merged_dmaqc_data$tissue = merged_dmaqc_data$sampletypedescription
# define the time to freeze variable
merged_dmaqc_data$time_to_freeze = merged_dmaqc_data$calculated.variables.time_death_to_collect_min +
merged_dmaqc_data$calculated.variables.time_collect_to_freeze_min
# col time vs. control
# df = data.frame(
# bid = merged_dmaqc_data$bid,
# edta_col_time = merged_dmaqc_data$calculated.variables.edta_coll_time,
# time_to_freeze = merged_dmaqc_data$time_to_freeze,
# is_control = merged_dmaqc_data$animal.key.is_control,
# tp = merged_dmaqc_data$animal.key.timepoint,
# tissue = merged_dmaqc_data$specimen.processing.sampletypedescription
# )
# df = unique(df)
# boxplot(edta_col_time/3600 ~ is_control,df)
# boxplot(edta_col_time/3600 - tp ~ is_control,df)
# wilcox.test(edta_col_time/3600 ~ is_control,df)
# blood freeze times
blood_samples =
merged_dmaqc_data$specimen.processing.sampletypedescription ==
"EDTA Plasma"
blood_freeze_time =
as.difftime(merged_dmaqc_data$specimen.processing.t_freeze,units = "mins") -
as.difftime(merged_dmaqc_data$specimen.processing.t_edtaspin,units="mins")
blood_freeze_time = as.numeric(blood_freeze_time)
time_to_freeze = merged_dmaqc_data$time_to_freeze[blood_samples] =
blood_freeze_time[blood_samples]
# Load our parsed metabolomics datasets
metabolomics_parsed_datasets = load_from_bucket(
file = "metabolomics_parsed_datasets_pass1a_external1.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/")[[1]]
# # # Read the dictionary
# dict_bucket =
# "gs://motrpac-external-release1-results/metabolomics_targeted/motrpac_metabolomics_data_dictionary-v1.1.5.txt"
# dict_download = get_single_file_from_bucket_to_local_dir(dict_bucket)
# metabolomics_dict = fread(dict_download[[1]],data.table = F)
# metabolomics_dict[grepl("gluc",metabolomics_dict[,2],ignore.case = T),]
Define the variables to be adjusted for:
biospec_cols = c(
"acute.test.distance",
"calculated.variables.time_to_freeze",
# "calculated.variables.edta_coll_time", # no need - see code above for blood
"bid" # required for matching datasets
)
differential_analysis_cols = c(
"animal.registration.sex",
"animal.key.timepoint",
"animal.key.is_control"
)
pipeline_qc_cols = c("sample_order")
Log-transform: effect on variance
Some sites do not use the log transformation on their dataset. In this section we plot the coefficient of variation as a function of the mean instensity. We take a single dataset as an example to show how log-transformed data have reduced dependency and smoother plots.
As an additional analysis we also plot the number of missing values per metabolite as a function of its mean intensity. We show that while there is high correlation some missing values appear in fairely high intensities. This is important for imputation as some sites use some fixed low value instead of knn imputation.
# Plot cv vs means
library(gplots)
d = metabolomics_parsed_datasets[["white_adipose_powder,metab_u_hilicpos,unnamed"]]
dx = d$sample_data
CoV<-function(x){return(sd(x,na.rm = T)/mean(x,na.rm=T))}
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Raw data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Repeat after log2
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Log2 data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Plot number of NAs vs intensity mean
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
num_nas = rowSums(is.na(dx))
df = data.frame(Num_NAs = num_nas[inds],Mean_intensity = dmeans[inds])
rho = cor(df$Num_NAs,df$Mean_intensity,method="spearman")
rho = format(rho,digits=2)
plot(Num_NAs~Mean_intensity,df,cex=0.5,pch=20,
main=paste("Spearman:",rho))

Data filtering and normalization
We go over each dataset and merge the named and unnamed subparts of the untargeted datasets. For targeted data, we merge datasets that are from the same site and tissue.
Then the preprocessing of the new merged datasets is as follows: (1) log the data (values are raw intensities), (2) remove rows with \(>20\%\) missing values, and (3) impute missing values using knn (k=10).
Important: datasets that do not have unique metabolite names or sample ids probably had errors while parsing and are therefore ignored. Also, untargeted with failed mergning of the unnamed and named subsets (e.g., due to different sample ids) are ignored as well.
Finally we add an additional version for each dataset by directly regressing out the sample order component. See https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4757603/ for more details (trend correction is also called background correction, and this is done for each batch separately).
metabolomics_processed_datasets = c()
# set a binary vector indicating with entries to skip
metabolomics_raw_datasets_skip = rep(F,length(metabolomics_parsed_datasets))
names(metabolomics_raw_datasets_skip) = names(metabolomics_parsed_datasets)
untargeted_merge_errors = list()
for(currname in names(metabolomics_parsed_datasets)){
if(metabolomics_raw_datasets_skip[currname]){next}
arr = strsplit(currname,split=",")[[1]]
arr = arr[-length(arr)]
name1 = paste(paste(arr,collapse=","),"named",sep=",")
name2 = paste(paste(arr,collapse=","),"unnamed",sep=",")
# If dataset is untargeted, merge named and unnamed, update the dataset
# currname. Skip the dataset if the merge fails, but store and print the errror.
# If the dataset is targeted - go over all datasets with the same vial labels and
# merge if possible.
if(name2 %in% names(metabolomics_parsed_datasets)){
d1 = metabolomics_parsed_datasets[[name1]]
d2 = metabolomics_parsed_datasets[[name2]]
newd = NULL
tryCatch({
newd = merge_named_and_unnamed_metabolomics_datasets(d1,d2,strict = F)
}, error = function(e) {}) # can add error handling here
currname = paste(paste(arr,collapse=","),"untargeted",sep=",")
metabolomics_raw_datasets_skip[name1] = T
metabolomics_raw_datasets_skip[name2] = T
if(is.null(newd)){
print(paste("# Skipping dataset",
currname,
"because merging the named and unnamed subsets failed"))
next
}
}
else{
metabolomics_raw_datasets_skip[currname] = T
newd = metabolomics_parsed_datasets[[currname]]
curr_vials = colnames(newd$sample_data)
curr_site = tolower(unique(newd$sample_meta$site))
for(another_dataset in names(metabolomics_raw_datasets_skip)){
if(metabolomics_raw_datasets_skip[another_dataset]){next}
another_d = metabolomics_parsed_datasets[[another_dataset]]
another_vials = colnames(another_d$sample_data)
another_d_site = tolower(unique(another_d$sample_meta$site))
another_shared_vials = intersect(another_vials,curr_vials)
if(curr_site == another_d_site &&
length(another_shared_vials)==length(another_vials) &&
length(another_shared_vials)==length(curr_vials)
){
# examine the metadata
curr_meta = newd$sample_meta
another_meta = another_d$sample_meta
# at this point we know that both datasets have the
# same samples, but not necessarily the same controls
# these has been partially submitted and we therefore need to
# intersect here
shared_meta_samples = intersect(rownames(curr_meta),rownames(another_meta))
# make sure that the vials, types, and order are the same
if(length(shared_meta_samples) < 1 ||
!all(curr_meta[shared_meta_samples,1:3]==
another_meta[shared_meta_samples,1:3])){
next # continue if cannot merge
}
try({
newd = merge_named_and_unnamed_metabolomics_datasets(
newd,another_d,strict = F) # no strict merge thanks to test above
metabolomics_raw_datasets_skip[another_dataset] = T
})
}
}
# update the name of the dataset
# we keep track of the number of datasets from the given site, as some
# merges may fail
currname = currname = paste(arr[1],curr_site,sep=",")
num_currname = sum(grepl(currname,names(metabolomics_processed_datasets)))
currname = paste(currname,num_currname+1,sep=",")
}
print(paste("#### Analyzing data from:",currname))
curr_data = newd$sample_data
curr_data2 = recast_numeric_data_frame(curr_data)
curr_data_mat = as.matrix(curr_data2)
print(paste("no errors in parsing numeric values:",
all(curr_data==curr_data_mat,na.rm=T)))
# floor the data at 0
curr_data_mat[curr_data_mat<0] = 0
# Keep a logged version of the data
curr_data_log = log(curr_data_mat+1,base=2)
# organize the metadata
curr_meta = merged_dmaqc_data[colnames(curr_data),
union(biospec_cols,differential_analysis_cols)]
# remove metadata variables with too many NAs
na_counts = apply(is.na(curr_meta),2,sum)
curr_meta = curr_meta[,na_counts/nrow(curr_meta) < 0.1]
# Look at the sample order
curr_order = newd$sample_meta[rownames(curr_meta),"sample_order"]
# organize the dataset
curr_data = curr_data[,rownames(curr_meta)]
curr_data_log = curr_data_log[,rownames(curr_meta)]
# remove zero variance rows or rows with many NAs
rows_to_rem = !(apply(curr_data,1,sd,na.rm=T)>0)
percent_na = rowSums(is.na(curr_data)) / ncol(curr_data)
print(paste("number of NA cells:",sum(is.na(curr_data))))
rows_to_rem = rows_to_rem | percent_na > 0.2
# remove rows with no annotation
rows_to_rem = rows_to_rem | newd$data_raw_rownames == ""
rows_to_rem = rows_to_rem | newd$data_raw_rownames == "-"
rows_to_rem = rows_to_rem | newd$data_raw_rownames == "_"
rows_to_rem = rows_to_rem | is.na(newd$data_raw_rownames)
# remove rows with duplicated representation
row_duplications = names(which(table(newd$data_raw_rownames)>1))
if(any(newd$data_raw_rownames[!rows_to_rem] %in% row_duplications)){
rows_to_rem = rows_to_rem | newd$data_raw_rownames %in% row_duplications
print("# WARNING: dataset has duplicated row names whose data are now ignored")
print(paste(row_duplications,collapse=","))
}
curr_data = curr_data[!rows_to_rem,]
curr_data_log = curr_data_log[!rows_to_rem,]
# impute - use capture.output to avoid prints
capture.output(
{curr_data_imp = impute::impute.knn(as.matrix(curr_data_log))$data}
,file=NULL)
curr_data_rnames = newd$data_raw_rownames[!rows_to_rem]
rownames(curr_data_imp) = curr_data_rnames
# Regress out the sample order
# lm_regress_matrix works on columns
curr_data_imp2 = t(lm_regress_out_matrix(t(curr_data_imp),curr_order))
# update the data object
metabolomics_processed_datasets[[currname]] = newd
metabolomics_processed_datasets[[currname]]$sample_data = curr_data_imp
metabolomics_processed_datasets[[currname]]$sample_data_nolog_noimp = curr_data
metabolomics_processed_datasets[[currname]]$data_raw_rownames = curr_data_rnames
metabolomics_processed_datasets[[currname]]$sample_meta_parsed = curr_meta
metabolomics_processed_datasets[[currname]]$sample_data_order_adj = curr_data_imp2
}
save_to_bucket(metabolomics_processed_datasets,
file="metabolomics_processed_datasets10282019.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/")
Alternatively load the result from the bucket to save time:
metabolomics_processed_datasets = load_from_bucket(
file="metabolomics_processed_datasets10282019.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/"
)[[1]]
Log-transform: effect on differential analysis
Untargeted data are typically log-transformed and analyzed using linear models. On the other hand, concentration data are sometimes analyzed with the same type of models but using the original data. This raises a problem if we wish to compare exact statistics from these data. In this section we perform residual analysis for single metabolites. Our goal is to identify if concentration data behaves “normally” when not log-transformed. The analysis below examines the residuals of the data after fitting linear models for each metabolite, adjusting for freeze time and sex. We then compare the results with and without the log-transformation, counting the number of metabolites with a significant evidence for non-normally distributed residuals.
# check for normality using the Kolmogorov-Smirnov test
is_normal_test<-function(v,samp=10000){
return(ks.test(v,"pnorm",mean(v,na.rm=T),sd(v,na.rm = T))$p.value)
}
# go over the named datasets, get a logged and an unlogged version of
# the data, use these as inputs for the regression
residual_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
if(grepl("untargeted",nn1)){next}
x_log = metabolomics_processed_datasets[[nn1]]$sample_data
x_unlog = 2^x_log
# take the covariates, ignore distances
x_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
curr_covs = x_meta[,intersect(colnames(x_meta),biospec_cols[2])]
curr_covs = data.frame(curr_covs,
sex=x_meta$animal.registration.sex)
# get the lm objects
curr_models = list()
for(tp in unique(x_meta$animal.key.timepoint)){
res_log = apply(
x_log,1,
pass1a_simple_differential_abundance,
tps = x_meta$animal.key.timepoint,tp=tp,
is_control = x_meta$animal.key.is_control,
covs = curr_covs,return_model=T
)
res_unlog = apply(
x_unlog,1,
pass1a_simple_differential_abundance,
tps = x_meta$animal.key.timepoint,tp=tp,
is_control = x_meta$animal.key.is_control,
covs = curr_covs,return_model=T
)
is_norm = cbind(
sapply(res_log,function(x)is_normal_test(residuals(x))),
sapply(res_unlog,function(x)is_normal_test(residuals(x)))
)
colnames(is_norm) = c("log","not log")
curr_models[[as.character(tp)]] = is_norm
}
residual_analysis_results[[nn1]] = curr_models
}
# Is there a significant difference between the two options?
log_vs_unlog_summ_mat = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)
wilcox.test(y[,1],y[,2],paired = T,alternative = "g")$p.value))
# Count the number of non-normal metabolites
num_nonnormal_log = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)sum(y[,1]<0.05)))
num_nonnormal_log =
num_nonnormal_log[,order(colnames(num_nonnormal_log))]
num_nonnormal_unlog = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)sum(y[,2]<0.05)))
num_nonnormal_unlog =
num_nonnormal_unlog[,order(colnames(num_nonnormal_unlog))]
library(corrplot)
par(mar = c(5,5,5,10))
corrplot(t(num_nonnormal_log)- t(num_nonnormal_unlog),
is.corr = F,tl.cex = 0.7)

Trageted vs. Untargeted: single metabolite comparison
Compute statistics for each dataset
Compare overlaps, effect sizes, and correlations within tissues. Compare targeted-untargeted pairs only. For differential analysis we use the same model as in the analysis above.
# helper function to transform a metabolomics matrix
# to that of its motrpac compound ids
extract_metab_data_from_row_annot<-function(x,row_annot_x){
# get the coloumn that has the row names
int_sizes = apply(row_annot_x,2,function(x,y)length(intersect(x,y)),y=rownames(x))
ind = which(int_sizes==max(int_sizes,na.rm = T))[1]
row_annot_x = row_annot_x[is.element(row_annot_x[,ind],set=rownames(x)),]
rownames(row_annot_x) = row_annot_x[,ind]
shared = intersect(rownames(row_annot_x),rownames(x))
x = x[shared,]
row_annot_x = row_annot_x[shared,]
rownames(x) = row_annot_x$motrpac_comp_name
return(x)
}
single_metabolite_corrs = list()
single_metabolite_de = c()
named2covered_shared_metabolites = list()
for(nn1 in names(metabolomics_processed_datasets)){
nn1_tissue = strsplit(nn1,split=",")[[1]][1]
nn1_tissue = gsub("_powder","",nn1_tissue)
if(grepl("untargeted",nn1)){next}
single_metabolite_corrs[[nn1]] = list()
named2covered_shared_metabolites[[nn1]] = NULL
for(nn2 in names(metabolomics_processed_datasets)){
if(nn2 == nn1){next}
if(!grepl("untargeted",nn2)){next}
nn2_tissue = strsplit(nn2,split=",")[[1]][1]
nn2_tissue = gsub("_powder","",nn2_tissue)
nn2_dataset = strsplit(nn2,split=",")[[1]][2]
if(nn1_tissue!=nn2_tissue){next}
# get the numeric datasets and their annotation
x = metabolomics_processed_datasets[[nn1]]$sample_data
y = metabolomics_processed_datasets[[nn2]]$sample_data
row_annot_x = metabolomics_processed_datasets[[nn1]]$row_annot
row_annot_y = metabolomics_processed_datasets[[nn2]]$row_annot
# transform metabolite names to the motrpac comp name
x = extract_metab_data_from_row_annot(x,row_annot_x)
y = extract_metab_data_from_row_annot(y,row_annot_y)
# align the sample sets
bid_y = merged_dmaqc_data[colnames(y),"bid"]
bid_x = merged_dmaqc_data[colnames(x),"bid"]
# step 1: merge samples from the same BID
if(length(unique(bid_x))!=length(bid_x)){
x = aggregate_repeated_samples(x,bid_x)
}
else{
colnames(x) = bid_x
}
if(length(unique(bid_y))!=length(bid_y)){
y = aggregate_repeated_samples(y,bid_y)
}else{
colnames(y) = bid_y
}
# step 2: use the shared bio ids
shared_bids = as.character(intersect(colnames(y),colnames(x)))
x = as.matrix(x[,shared_bids])
y = as.matrix(y[,shared_bids])
# At this point x and y are over the same BIDs, now we add the metadata
y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
rownames(y_meta) = y_meta$bid
y_meta = y_meta[shared_bids,]
# get the shared matebolites
shared_metabolites = intersect(rownames(x),rownames(y))
shared_metabolites = na.omit(shared_metabolites)
if(length(shared_metabolites)==0){next}
named2covered_shared_metabolites[[nn1]] = union(
named2covered_shared_metabolites[[nn1]],
shared_metabolites
)
# Compute the correlation matrices of the shared metabolites
if(length(shared_metabolites)>1){
corrs =cor(t(x[shared_metabolites,]),
t(y[shared_metabolites,]),method = "spearman")
}
else{
corrs = cor(x[shared_metabolites,],
y[shared_metabolites,],method = "spearman")
}
# take the covariates (ignore distances)
curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
curr_covs = data.frame(y_meta[,curr_cov_cols])
names(curr_covs) = curr_cov_cols
curr_covs$sex = y_meta$animal.registration.sex # add sex
# differential analysis
for(tp in unique(y_meta$animal.key.timepoint)){
resx = t(apply(
matrix(x[shared_metabolites,],nrow=length(shared_metabolites)),1,
pass1a_simple_differential_abundance,
tps = y_meta$animal.key.timepoint,tp=tp,
is_control = y_meta$animal.key.is_control,
covs = curr_covs,return_model=F
))
resy = t(apply(
matrix(y[shared_metabolites,],nrow=length(shared_metabolites)),1,
pass1a_simple_differential_abundance,
tps = y_meta$animal.key.timepoint,tp=tp,
is_control = y_meta$animal.key.is_control,
covs = curr_covs,return_model=F
))
# Add dataset information, time point, tissue
# These are important annotations for our summary matrix
# called single_metabolite_de below
added_columns = matrix(cbind(
rep(nn1,length(shared_metabolites)),
rep(nn2,length(shared_metabolites)),
shared_metabolites,
rep(tp,length(shared_metabolites)),
rep(nn1_tissue,length(shared_metabolites))
),nrow=length(shared_metabolites))
resx = cbind(resx,rep(T,nrow(resx)))
colnames(resx)[ncol(resx)] = "is_targeted"
resy = cbind(resy,rep(F,nrow(resy)))
colnames(resy)[ncol(resy)] = "is_targeted"
if(nrow(resx)>1){
resx = cbind(added_columns[,-2],resx)
resy = cbind(added_columns[,-1],resy)
}
else{
resx = c(added_columns[,-2],resx)
resy = c(added_columns[,-1],resy)
}
single_metabolite_de = rbind(single_metabolite_de,resx)
single_metabolite_de = rbind(single_metabolite_de,resy)
}
single_metabolite_corrs[[nn1]][[nn2]] = corrs
}
}
# Reformat the results for easier comparison later
single_metabolite_de = data.frame(single_metabolite_de)
names(single_metabolite_de) = c(
"dataset","metabolite","tp","tissue",
"Est","Std","Tstat","Pvalue","is_targeted")
for(col in names(single_metabolite_de)[-c(1:4)]){
single_metabolite_de[[col]] = as.numeric(
as.character(single_metabolite_de[[col]]))
}
for(col in names(single_metabolite_de)[1:4]){
single_metabolite_de[[col]] =
as.character(single_metabolite_de[[col]])
}
# Remove duplications
rownames(single_metabolite_de) = NULL
for(nn in names(single_metabolite_de)){
ndig = 5
if(grepl("pval",nn,ignore.case = T)){
ndig = 10
}
if(is.numeric(single_metabolite_de[[nn]])){
single_metabolite_de[[nn]] = round(single_metabolite_de[[nn]],digits = ndig)
}
}
single_metabolite_de = unique(single_metabolite_de)
We next transform the data above into tables that contain data for each combination of metabolite, time point, and tissue. These are then used for different meta-analyses: (1) a simple random effects analysis, (2) random effects with a binary covariate indicating if a dataset is targeted or untargeted, (3) redo the RE model of (1) with the targeted data only, and (4) redo the RE model of (1) with the untargeted data only.
library(metafor)
meta_analysis_stats = list()
for(tissue in unique(single_metabolite_de$tissue)){
for(tp in unique(single_metabolite_de$tp)){
curr_subset = single_metabolite_de[
single_metabolite_de$tissue==tissue &
single_metabolite_de$tp==tp,]
for(metabolite in unique(curr_subset$metabolite)){
curr_met_data = curr_subset[
curr_subset$metabolite==metabolite,]
curr_met_data$var = curr_met_data$Std^2
re_model1 = NULL;re_model2=NULL
re_model_tar = NULL;re_model_untar = NULL
try({re_model1 = rma.uni(curr_met_data$Est,curr_met_data$var)})
try({re_model2 = rma.mv(curr_met_data$Est,curr_met_data$var,
mods=curr_met_data$is_targeted)})
try({re_model_tar = rma.uni(
curr_met_data[curr_met_data$is_targeted==1,"Est"],
curr_met_data[curr_met_data$is_targeted==1,"var"]
)})
try({re_model_untar = rma.uni(
curr_met_data[curr_met_data$is_targeted==0,"Est"],
curr_met_data[curr_met_data$is_targeted==0,"var"]
)})
meta_analysis_stats[[paste(metabolite,tissue,tp,sep=",")]] =
list(curr_met_data=curr_met_data,re_model1=re_model1,
re_model2 = re_model2,re_model_tar=re_model_tar,
re_model_untar = re_model_untar)
}
}
}
Error in rma.uni(curr_met_data$Est, curr_met_data$var) :
Fisher scoring algorithm did not converge. See 'help(rma)' for possible remedies.
Comparison results
We now show some plots to summarize the comparison.
Dataset coverage
We first plot the number and percentage of metabolites in the targeted datasets that are measured in at least one untargeted dataset.
library(ggplot2)
dataset2num_metabolites = sapply(metabolomics_processed_datasets,
function(x)nrow(x$sample_data))
named_dataset_coverage = sapply(named2covered_shared_metabolites,length)
named_dataset_coverage = data.frame(
name = names(named_dataset_coverage),
percentage = named_dataset_coverage /
dataset2num_metabolites[names(named_dataset_coverage)],
count = named_dataset_coverage,
total = dataset2num_metabolites[names(named_dataset_coverage)]
)
# add datasets with no coverage
all_targeted_datasets = names(metabolomics_processed_datasets)
all_targeted_datasets = all_targeted_datasets[!grepl("untarg",all_targeted_datasets)]
zero_coverage_datasets = setdiff(all_targeted_datasets,
named_dataset_coverage$name)
zero_coverage_datasets = data.frame(
name = zero_coverage_datasets,
percentage = rep(0,length(zero_coverage_datasets)),
count = rep(0,length(zero_coverage_datasets)),
total = dataset2num_metabolites[zero_coverage_datasets]
)
named_dataset_coverage = rbind(named_dataset_coverage,
zero_coverage_datasets)
named_dataset_coverage =
named_dataset_coverage[order(as.character(named_dataset_coverage$name)),]
print(ggplot(named_dataset_coverage, aes(x=name, y=percentage)) +
geom_bar(stat = "identity",width=0.2) + coord_flip() +
geom_text(data=named_dataset_coverage,
aes(name, percentage+0.05, label=count),
position = position_dodge(width=0.9),
size=4) +
ggtitle("Targeted dataset: coverage by untargeted"))
Spearman correlations
We examine the average absolute correlation between the platforms (within tissues). Whenever two platforms share more than a single metabolite we plot both the average correlation between the same metabolites and between other metabolites. Adding the average correlation between platforms but with different metabolites is important as it gives some perspective to what a significant correlation is. That is, in many cases below, the average correlation may be greater than expected.
# Next examine the Spearman correlations between platforms
mean_abs<-function(x,...){return(mean(abs(x),...))}
sd_abs<-function(x,...){return(sd(abs(x),...))}
extract_diag_vs_non_diag<-function(corrs,func=mean_abs,...){
if(length(corrs)==1){
return(c(same=func(corrs,...),other=NA))
}
same = func(diag(corrs),...)
other = func(
c(corrs[lower.tri(corrs,diag = F)]),...)
return(c(same=same,other=other))
}
for(tar_dataset in names(single_metabolite_corrs)){
l = single_metabolite_corrs[[tar_dataset]]
if(length(l)==0){next}
corr_info = as.data.frame(t(sapply(l, extract_diag_vs_non_diag)))
corr_sd = as.data.frame(t(sapply(l, extract_diag_vs_non_diag,func=sd_abs)))
rownames(corr_info) = sapply(rownames(corr_info),
function(x)strsplit(x,split=",")[[1]][2])
rownames(corr_info) = gsub("metab_u_","",rownames(corr_info))
rownames(corr_sd) = rownames(corr_info)
corr_info$dataset = rownames(corr_info)
corr_sd$dataset = corr_info$dataset
corr_info = melt(corr_info)
corr_sd = melt(corr_sd)
corr_info$sd = corr_sd$value
print(
ggplot(corr_info, aes(x=dataset, y=value, fill=variable)) +
geom_bar(position=position_dodge(), stat="identity", colour='black') +
geom_errorbar(aes(ymin=value-sd, ymax=value+sd),na.rm=T,
width=.2,position=position_dodge(.9)) +
ggtitle(tar_dataset) + xlab("Untargeted dataset") + ylab("Spearman") +
labs(fill = "Pair type") +
theme(legend.position="top",legend.direction = "horizontal")
)
}




























Meta-analysis of differential effects
Here we go into the single metabolites comparison in greater detail (again, within tissues).
We start with a few examples. Here are the results for lactate in plasma.







We can now check the same analysis for liver:
lact_res = meta_analysis_stats[
grepl("lact",names(meta_analysis_stats),ignore.case = T) &
grepl("liver",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
forest(meta_analysis_stats[[lact_example]]$re_model1,
slab = meta_analysis_stats[[lact_example]][[1]][,1],
main = lact_example,xlab = "Log fc",
col = "blue",cex = 1.1)
}






We now move to a systematic comparison of the datasets. We start with a naive comparison, checking if metabolites are consistently being discovered using a simple \(p=0.001\) threshold for significance.
As a more rigorous analysis, we use the meta-analyses to obtain useful statistics for comparison.
# Extract some useful statistics per analysis
# P-value for the difference between targeted and untargeted
targeted_diff_p =
sapply(meta_analysis_stats,function(x)x$re_model2$pval[2])
# P-values - targeted vs. untargeted
pvals_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$pval)
pvals_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$pval)
pvals_untar = unlist(pvals_untar[sapply(pvals_untar,length)>0])
significant_in = rep("None",length(pvals_untar))
significant_in[pvals_tar<0.001] = "Targeted"
significant_in[pvals_untar<0.001] = "Untargeted"
significant_in[pvals_tar<0.001 & pvals_untar<0.001] = "Both"
significant_diff = targeted_diff_p<0.001
df = data.frame(
targeted = -log10(pvals_tar),
untargeted = -log10(pvals_untar),
significant_in = significant_in,
significant_diff = significant_diff
)
rho = cor(pvals_tar,pvals_untar,method = "spearman")
rhop = cor.test(pvals_tar,pvals_untar,method = "spearman")$p.value
print(
ggplot(df, aes(x=targeted, y=untargeted,
shape=significant_diff, color=significant_in)) +
geom_point() +
ggtitle(paste("-log10 p-values, spearman:",format(rho,digits=2),
"(p=",format(rhop,digits=3),")"))
)
print("### Summary of differences in RE models ###")
print("Model is significant at p<0.001:")
print(table(df$significant_in))
print("Adding is_targeted as a covariate has p<0.001:")
print(table(df$significant_diff))
# Betas - targeted vs. untargeted
betas_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$beta[1,1])
betas_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$beta[1,1])
betas_untar = unlist(betas_untar[sapply(betas_untar,length)>0])
df = data.frame(
targeted = betas_tar,
untargeted = betas_untar,
significant_in = significant_in,
significant_diff = significant_diff
)
rho = cor(betas_untar,betas_tar,method = "spearman")
rhop = cor.test(betas_untar,betas_tar,method = "spearman")$p.value
print(
ggplot(df, aes(x=targeted, y=untargeted,
shape=significant_diff, color=significant_in)) +
geom_point() +
ggtitle(paste("Effect sizes, spearman:",format(rho,digits=2),
"(p=",format(rhop,digits=3),")"))
)
From the plots above we take the most extreme examples and examine their forest plots.
agree_example = names(sample(which(pvals_tar< 1e-10 & pvals_untar < 1e-10 &
targeted_diff_p > 0.1))[1])
forest(meta_analysis_stats[[agree_example]]$re_model1,
slab = meta_analysis_stats[[agree_example]][[1]][,1],
main = paste(agree_example,"significant in both, tar and untar agree",sep="\n"),
xlab = "Log fc",col = "blue")
agree_p_disagree_beta = names(sample(which(pvals_tar< 1e-10 & pvals_untar < 1e-10 &
targeted_diff_p < 0.001))[1])
forest(meta_analysis_stats[[agree_p_disagree_beta]]$re_model1,
slab = meta_analysis_stats[[agree_p_disagree_beta]][[1]][,1],
main = paste(agree_p_disagree_beta,
"significant in both, tar and untar disagree",sep="\n"),
xlab = "Log fc",col = "blue")
disagree_example1 = names(sample(which(pvals_tar< 1e-20 & pvals_untar >0.1))[1])
forest(meta_analysis_stats[[disagree_example1]]$re_model1,
slab = meta_analysis_stats[[disagree_example1]][[1]][,1],
main = paste(disagree_example1,
"significant in both, tar and untar agree",sep="\n"),
xlab = "Log fc",col = "blue")
disagree_example2 = names(sample(which(pvals_tar > 0.1 & pvals_untar < 1e-20))[1])
forest(meta_analysis_stats[[disagree_example2]]$re_model1,
slab = meta_analysis_stats[[disagree_example2]][[1]][,1],
main = paste(disagree_example2,
"significant in both, tar and untar agree",sep="\n"),
xlab = "Log fc",col = "blue")
Targeted vs. untargeted: comparison as a prediction task
Use 10-fold cross validation for analysis within tissues.
nfolds = 5
prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
nn1_tissue = strsplit(nn1,split=",")[[1]][1]
nn1_tissue = gsub("_powder","",nn1_tissue)
if(grepl("untargeted",nn1)){next}
for(nn2 in names(metabolomics_processed_datasets)){
if(nn2 == nn1){next}
if(!grepl("untargeted",nn2)){next}
nn2_tissue = strsplit(nn2,split=",")[[1]][1]
nn2_tissue = gsub("_powder","",nn2_tissue)
nn2_dataset = strsplit(nn2,split=",")[[1]][2]
if(nn1_tissue!=nn2_tissue){next}
print(paste("features from:",nn2))
print(paste("labels from:",nn1))
# get the numeric datasets and their annotation
y = metabolomics_processed_datasets[[nn1]]$sample_data
x = metabolomics_processed_datasets[[nn2]]$sample_data
# align the sample sets
bid_y = merged_dmaqc_data[colnames(y),"bid"]
bid_x = merged_dmaqc_data[colnames(x),"bid"]
# step 1: merge samples from the same BID
if(length(unique(bid_x))!=length(bid_x)){
x = aggregate_repeated_samples(x,bid_x)
}
else{
colnames(x) = bid_x
}
if(length(unique(bid_y))!=length(bid_y)){
y = aggregate_repeated_samples(y,bid_y)
}else{
colnames(y) = bid_y
}
# step 2: use the shared bio ids
shared_bids = as.character(intersect(colnames(y),colnames(x)))
x = t(as.matrix(x[,shared_bids]))
y = t(as.matrix(y[,shared_bids]))
# At this point x and y are over the same BIDs, now we add the metadata
y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
rownames(y_meta) = y_meta$bid
y_meta = y_meta[shared_bids,]
# take the covariates (ignore distances)
curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
curr_covs = data.frame(y_meta[,curr_cov_cols])
names(curr_covs) = curr_cov_cols
curr_covs$sex = y_meta$animal.registration.sex # add sex
# add the covariates into x
x = cbind(x,curr_covs)
# Run the regressions
folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
numFeatures = min(ncol(x),2000)
preds = c();real=c()
for(i in 1:ncol(y)){
if( i %% 10 == 0){print(paste("analyzing metabolite number:",i))}
y_i = y[,1]
i_preds = c();i_real=c()
for(j in 1:nfolds){
tr_x = x[folds!=j,]
tr_yi = y_i[folds!=j]
te_x = x[folds==j,]
te_y = y_i[folds==j]
# random forest
# model = randomForest(tr_yi,x=tr_x,ntree = 20)
# te_preds = predict(model,newdata = te_x)
model = feature_selection_wrapper(tr_x,tr_yi,
coeff_of_var,randomForest,
topK = numFeatures,ntree=50)
te_preds = predict(model,newdata = te_x)
i_preds = c(i_preds,te_preds)
i_real = c(i_real,te_y)
}
preds = cbind(preds,i_preds)
real = cbind(real,i_real)
}
currname = paste(nn1,nn2,sep=";")
prediction_analysis_results[[currname]] = list(
preds = preds,real=real
)
}
}
names(prediction_analysis_results)
cov_prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
print(nn1)
y = metabolomics_processed_datasets[[nn1]]$sample_data
y_vials = colnames(y)
bid_y = merged_dmaqc_data[colnames(y),"bid"]
colnames(y) = bid_y
y = t(as.matrix(y))
if(ncol(y)>1000){next}
cov_cols = c("animal.registration.sex",
"acute.test.weight",
"acute.test.distance",
"animal.key.timepoint")
covs = merged_dmaqc_data[y_vials,cov_cols]
x = covs
# Run the regressions
folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
numFeatures = min(ncol(x),2000)
preds = c();real=c()
for(i in 1:ncol(y)){
y_i = y[,1]
i_preds = c();i_real=c()
for(j in 1:nfolds){
print(j)
tr_x = x[folds!=j,]
tr_yi = y_i[folds!=j]
te_x = x[folds==j,]
te_y = y_i[folds==j]
# random forest
model = randomForest(tr_yi,x=tr_x,ntree = 20)
te_preds = predict(model,newdata = te_x)
i_preds = c(i_preds,te_preds)
i_real = c(i_real,te_y)
}
preds = cbind(preds,i_preds)
real = cbind(real,i_real)
}
cov_prediction_analysis_results[[nn1]] = list(
preds = preds,real=real
)
}
results_metrics = c()
for(nn in names(prediction_analysis_results)){
preds = prediction_analysis_results[[nn]]$preds
real = prediction_analysis_results[[nn]]$real
rhos1 = diag(cor(preds,real))
rhos2 = diag(cor(preds,real,method="spearman"))
SEs = (preds-real)^2
mse = mean(SEs)
normSEs = SEs / apply(real,2,sd)
curr_scores = c(mean(rhos1),mean(rhos2),min(rhos1),min(rhos2),
mse,mean(normSEs))
names(curr_scores) = c("mean_rho","mean_spearman_rho","min_rho","min_spearman_rho",
"MSE","mean MSE/SD")
results_metrics = rbind(results_metrics,
curr_scores)
rownames(results_metrics)[nrow(results_metrics)] = nn
}
cov_results_metrics = c()
for(nn in names(cov_prediction_analysis_results)){
preds = cov_prediction_analysis_results[[nn]]$preds
real = cov_prediction_analysis_results[[nn]]$real
rhos1 = diag(cor(preds,real))
rhos2 = diag(cor(preds,real,method="spearman"))
SEs = (preds-real)^2
mse = mean(SEs)
normSEs = SEs / apply(real,2,sd)
curr_scores = c(mean(rhos1),mean(rhos2),min(rhos1),min(rhos2),
mse,mean(normSEs))
names(curr_scores) = c("mean_rho","mean_spearman_rho","min_rho","min_spearman_rho",
"MSE","mean MSE/SD")
cov_results_metrics = rbind(cov_results_metrics,
curr_scores)
rownames(cov_results_metrics)[nrow(cov_results_metrics)] = nn
}
# Some boxplots
pred_targets = sapply(names(prediction_analysis_results),function(x)
strsplit(x,split = ";")[[1]][1])
target = "liver_powder,metab_t_tca,named"
curr_res = unique(results_metrics[pred_targets == target,])
rownames(curr_res) = gsub(target,"",rownames(curr_res))
rownames(curr_res) = gsub(";","",rownames(curr_res))
rownames(curr_res)[rownames(curr_res)==""] = target
cov_baseline = cov_results_metrics[target,]
par(mar = c(8,4,2,2))
cols = rep("blue",nrow(curr_res))
cols[rownames(curr_res)==target] = "black"
plt = barplot(curr_res[,4],beside = T,xaxt="n",legend=F,
ylab="Min rho (Spearman)",
ylim = c(0.5,1),xpd=F,col=cols,
main = target)
text(plt, par("usr")[3], labels = rownames(curr_res),
srt = 45, adj = c(1.1,1.1), xpd = T, cex=0.6)
abline(h=cov_baseline[4],lty=2,lwd=2,col="red")
par(mar = c(8,4,2,2))
plt = barplot(curr_res[,6],beside = T,xaxt="n",legend=F,
ylab="Mean standardized SE",xpd=F,
main = target,col=cols)
text(plt, par("usr")[3], labels = rownames(curr_res),
srt = 45, adj = c(1.1,1.1), xpd = T, cex=0.6)
abline(h=cov_baseline[6],lty=2,lwd=2,col="red")
# preds = c();real=c()
# for(j in 1:nfolds){
# tr_x = x[folds!=j,]
# tr_y = y[folds!=j,]
# te_x = x[folds==j,]
# te_y = y[folds==j,]
# model = MTL_wrapper(tr_x,tr_y,type="Regression", Regularization="L21")
# te_preds = predict(model,te_x)
# real = rbind(real,te_y)
# preds = rbind(preds,te_preds)
# }
# diag(cor(preds,real))
# Using PLS regression
# library(pls)
# pls_model = plsr(y~x,ncomp = 5,validation="LOO")
# eval = MSEP(pls_model)
#
# y_pca = prcomp(y)
# plot(y_pca)
# explained_var = y_pca$sdev^2/sum(y_pca$sdev^2)
# y_pca_matrix = y_pca$x[,1:10]
#
# # regress out sex, weight
#
# get_explained_variance_using_PCA(x,y)
# x = apply(x,2,regress_out,covs=covs)
# y = apply(y,2,regress_out,covs=covs)
# get_explained_variance_using_PCA(x,y)
LS0tCnRpdGxlOiAiQklDIG1ldGFib2xvbWljcyBkYXRhIGFuYWx5c2lzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCkluIHRoaXMgZG9jdW1lbnQgd2UgcHJlc2VudCB0aGUgam9pbnQgYW5hbHlzaXMgb2YgdGhlIFBBU1MxQSBtZXRhYm9sb21pY3MgZGF0YXNldHMuCgojIExvYWQgYWxsIGRhdGFzZXRzCgpMb2FkIHRoZSBkYXRhIGZyb20gdGhlIGNsb3VkLCBpbmNsdWRpbmc6IHBoZW5vdHlwaWMgZGF0YSwgbWV0YWJvbG9taWMgZGF0YXNldHMsIGFuZCBtZXRhYm9sb21pY3MgZGljdGlvbmFyeS4KCmBgYHtyLHJlc3VsdHM9J2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Kc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9zdXBlcnZpc2VkX25vcm1hbGl6YXRpb25fZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL3Vuc3VwZXJ2aXNlZF9ub3JtYWxpemF0aW9uX2Z1bmN0aW9ucy5SIikKc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9nY3BfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL2Fzc29jaWF0aW9uX2FuYWx5c2lzX21ldGhvZHMuUiIpCnNvdXJjZSgifi9EZXNrdG9wL3JlcG9zL21vdHJwYWMtYmljLW5vcm0tcWMvdG9vbHMvZGF0YV9hdXhfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjL3Rvb2xzL3ByZWRpY3Rpb25fbWxfdG9vbHMuUiIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIGZvciBjbGFzc2lmaWNhdGlvbiB0ZXN0cwoKIyBMb2FkIHRoZSBkbWFxYyBkYXRhCm1lcmdlZF9kbWFxY19kYXRhID0gIGxvYWRfZnJvbV9idWNrZXQoIm1lcmdlZF9kbWFxY19kYXRhMjAxOS0xMC0xNS5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvcGhlbm9fZG1hcWMvIixGKQptZXJnZWRfZG1hcWNfZGF0YSA9IG1lcmdlZF9kbWFxY19kYXRhW1sxXV0Kcm93bmFtZXMobWVyZ2VkX2RtYXFjX2RhdGEpID0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kbWFxY19kYXRhJHZpYWxfbGFiZWwpCiMgZGVmaW5lIHRoZSB0aXNzdWUgdmFyaWFibGUKbWVyZ2VkX2RtYXFjX2RhdGEkdGlzc3VlID0gbWVyZ2VkX2RtYXFjX2RhdGEkc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgZGVmaW5lIHRoZSB0aW1lIHRvIGZyZWV6ZSB2YXJpYWJsZQptZXJnZWRfZG1hcWNfZGF0YSR0aW1lX3RvX2ZyZWV6ZSA9IG1lcmdlZF9kbWFxY19kYXRhJGNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfZGVhdGhfdG9fY29sbGVjdF9taW4gKyAKICBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy50aW1lX2NvbGxlY3RfdG9fZnJlZXplX21pbgoKIyBjb2wgdGltZSB2cy4gY29udHJvbAojIGRmID0gZGF0YS5mcmFtZSgKIyAgIGJpZCA9IG1lcmdlZF9kbWFxY19kYXRhJGJpZCwKIyAgIGVkdGFfY29sX3RpbWUgPSBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSwKIyAgIHRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemUsCiMgICBpc19jb250cm9sID0gbWVyZ2VkX2RtYXFjX2RhdGEkYW5pbWFsLmtleS5pc19jb250cm9sLAojICAgdHAgPSBtZXJnZWRfZG1hcWNfZGF0YSRhbmltYWwua2V5LnRpbWVwb2ludCwKIyAgIHRpc3N1ZSA9IG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgKQojIGRmID0gdW5pcXVlKGRmKQojIGJveHBsb3QoZWR0YV9jb2xfdGltZS8zNjAwIH4gaXNfY29udHJvbCxkZikKIyBib3hwbG90KGVkdGFfY29sX3RpbWUvMzYwMCAtIHRwIH4gaXNfY29udHJvbCxkZikKIyB3aWxjb3gudGVzdChlZHRhX2NvbF90aW1lLzM2MDAgfiBpc19jb250cm9sLGRmKQoKIyBibG9vZCBmcmVlemUgdGltZXMKYmxvb2Rfc2FtcGxlcyA9IAogIG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uID09CiAgIkVEVEEgUGxhc21hIgpibG9vZF9mcmVlemVfdGltZSA9IAogIGFzLmRpZmZ0aW1lKG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3NpbmcudF9mcmVlemUsdW5pdHMgPSAibWlucyIpIC0KICBhcy5kaWZmdGltZShtZXJnZWRfZG1hcWNfZGF0YSRzcGVjaW1lbi5wcm9jZXNzaW5nLnRfZWR0YXNwaW4sdW5pdHM9Im1pbnMiKQpibG9vZF9mcmVlemVfdGltZSA9IGFzLm51bWVyaWMoYmxvb2RfZnJlZXplX3RpbWUpCnRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemVbYmxvb2Rfc2FtcGxlc10gPSAKICBibG9vZF9mcmVlemVfdGltZVtibG9vZF9zYW1wbGVzXQoKIyBMb2FkIG91ciBwYXJzZWQgbWV0YWJvbG9taWNzIGRhdGFzZXRzCm1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHMgPSBsb2FkX2Zyb21fYnVja2V0KAogIGZpbGUgPSAibWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c19wYXNzMWFfZXh0ZXJuYWwxLlJEYXRhIiwKICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpW1sxXV0KCiMgIyAjIFJlYWQgdGhlIGRpY3Rpb25hcnkKIyBkaWN0X2J1Y2tldCA9CiMgICAiZ3M6Ly9tb3RycGFjLWV4dGVybmFsLXJlbGVhc2UxLXJlc3VsdHMvbWV0YWJvbG9taWNzX3RhcmdldGVkL21vdHJwYWNfbWV0YWJvbG9taWNzX2RhdGFfZGljdGlvbmFyeS12MS4xLjUudHh0IgojIGRpY3RfZG93bmxvYWQgPSBnZXRfc2luZ2xlX2ZpbGVfZnJvbV9idWNrZXRfdG9fbG9jYWxfZGlyKGRpY3RfYnVja2V0KQojIG1ldGFib2xvbWljc19kaWN0ID0gZnJlYWQoZGljdF9kb3dubG9hZFtbMV1dLGRhdGEudGFibGUgPSBGKQojIG1ldGFib2xvbWljc19kaWN0W2dyZXBsKCJnbHVjIixtZXRhYm9sb21pY3NfZGljdFssMl0saWdub3JlLmNhc2UgPSBUKSxdCmBgYApEZWZpbmUgdGhlIHZhcmlhYmxlcyB0byBiZSBhZGp1c3RlZCBmb3I6CgpgYGB7cn0KYmlvc3BlY19jb2xzID0gYygKICAiYWN1dGUudGVzdC5kaXN0YW5jZSIsCiAgImNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfdG9fZnJlZXplIiwKICAjICJjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSIsICMgbm8gbmVlZCAtIHNlZSBjb2RlIGFib3ZlIGZvciBibG9vZAogICJiaWQiICMgcmVxdWlyZWQgZm9yIG1hdGNoaW5nIGRhdGFzZXRzCiAgKQpkaWZmZXJlbnRpYWxfYW5hbHlzaXNfY29scyA9IGMoCiAgImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwKICAiYW5pbWFsLmtleS50aW1lcG9pbnQiLAogICJhbmltYWwua2V5LmlzX2NvbnRyb2wiCikKcGlwZWxpbmVfcWNfY29scyA9IGMoInNhbXBsZV9vcmRlciIpCmBgYAoKIyBMb2ctdHJhbnNmb3JtOiBlZmZlY3Qgb24gdmFyaWFuY2UKClNvbWUgc2l0ZXMgZG8gbm90IHVzZSB0aGUgbG9nIHRyYW5zZm9ybWF0aW9uIG9uIHRoZWlyIGRhdGFzZXQuIEluIHRoaXMgc2VjdGlvbiB3ZSBwbG90IHRoZSBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24gYXMgYSBmdW5jdGlvbiBvZiB0aGUgbWVhbiBpbnN0ZW5zaXR5LiBXZSB0YWtlIGEgc2luZ2xlIGRhdGFzZXQgYXMgYW4gZXhhbXBsZSB0byBzaG93IGhvdyBsb2ctdHJhbnNmb3JtZWQgZGF0YSBoYXZlIHJlZHVjZWQgZGVwZW5kZW5jeSBhbmQgc21vb3RoZXIgcGxvdHMuCgpBcyBhbiBhZGRpdGlvbmFsIGFuYWx5c2lzIHdlIGFsc28gcGxvdCB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIHBlciBtZXRhYm9saXRlIGFzIGEgZnVuY3Rpb24gb2YgaXRzIG1lYW4gaW50ZW5zaXR5LiBXZSBzaG93IHRoYXQgd2hpbGUgdGhlcmUgaXMgaGlnaCBjb3JyZWxhdGlvbiBzb21lIG1pc3NpbmcgdmFsdWVzIGFwcGVhciBpbiBmYWlyZWx5IGhpZ2ggaW50ZW5zaXRpZXMuIFRoaXMgaXMgaW1wb3J0YW50IGZvciBpbXB1dGF0aW9uIGFzIHNvbWUgc2l0ZXMgdXNlIHNvbWUgZml4ZWQgbG93IHZhbHVlIGluc3RlYWQgb2Yga25uIGltcHV0YXRpb24uCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgojIFBsb3QgY3YgdnMgbWVhbnMKbGlicmFyeShncGxvdHMpCmQgPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1sid2hpdGVfYWRpcG9zZV9wb3dkZXIsbWV0YWJfdV9oaWxpY3Bvcyx1bm5hbWVkIl1dCmR4ID0gZCRzYW1wbGVfZGF0YQpDb1Y8LWZ1bmN0aW9uKHgpe3JldHVybihzZCh4LG5hLnJtID0gVCkvbWVhbih4LG5hLnJtPVQpKX0KZG1lYW5zID0gYXBwbHkoZHgsMSxtZWFuLG5hLnJtPVQpCkNvVnMgPSBhcHBseShkeCwxLENvVikKaW5kcyA9ICFpcy5uYShDb1ZzKQpkZiA9IGRhdGEuZnJhbWUoTWVhbl9pbnRlbnNpdHkgPSBkbWVhbnNbaW5kc10sQ29WID0gQ29Wc1tpbmRzXSkKcGxvdChDb1Z+TWVhbl9pbnRlbnNpdHksZGYsY2V4PTAuNSxwY2g9MjAsbWFpbj0iUmF3IGRhdGEiKQpsaW5lcyhsb3dlc3MoQ29Wfk1lYW5faW50ZW5zaXR5LGRmKSxsdHk9Mixsd2Q9Mixjb2w9ImJsdWUiKQoKIyBSZXBlYXQgYWZ0ZXIgbG9nMgpkeCA9IGxvZygxK2Qkc2FtcGxlX2RhdGEsYmFzZT0yKQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKQ29WcyA9IGFwcGx5KGR4LDEsQ29WKQppbmRzID0gIWlzLm5hKENvVnMpCmRmID0gZGF0YS5mcmFtZShNZWFuX2ludGVuc2l0eSA9IGRtZWFuc1tpbmRzXSxDb1YgPSBDb1ZzW2luZHNdKQpwbG90KENvVn5NZWFuX2ludGVuc2l0eSxkZixjZXg9MC41LHBjaD0yMCxtYWluPSJMb2cyIGRhdGEiKQpsaW5lcyhsb3dlc3MoQ29Wfk1lYW5faW50ZW5zaXR5LGRmKSxsdHk9Mixsd2Q9Mixjb2w9ImJsdWUiKQoKIyBQbG90IG51bWJlciBvZiBOQXMgdnMgaW50ZW5zaXR5IG1lYW4KZHggPSBsb2coMStkJHNhbXBsZV9kYXRhLGJhc2U9MikKZG1lYW5zID0gYXBwbHkoZHgsMSxtZWFuLG5hLnJtPVQpCm51bV9uYXMgPSByb3dTdW1zKGlzLm5hKGR4KSkKZGYgPSBkYXRhLmZyYW1lKE51bV9OQXMgPSBudW1fbmFzW2luZHNdLE1lYW5faW50ZW5zaXR5ID0gZG1lYW5zW2luZHNdKQpyaG8gPSBjb3IoZGYkTnVtX05BcyxkZiRNZWFuX2ludGVuc2l0eSxtZXRob2Q9InNwZWFybWFuIikKcmhvID0gZm9ybWF0KHJobyxkaWdpdHM9MikKcGxvdChOdW1fTkFzfk1lYW5faW50ZW5zaXR5LGRmLGNleD0wLjUscGNoPTIwLAogICAgIG1haW49cGFzdGUoIlNwZWFybWFuOiIscmhvKSkKCgpgYGAKCiMgRGF0YSBmaWx0ZXJpbmcgYW5kIG5vcm1hbGl6YXRpb24KCldlIGdvIG92ZXIgZWFjaCBkYXRhc2V0IGFuZCBtZXJnZSB0aGUgbmFtZWQgYW5kIHVubmFtZWQgc3VicGFydHMgb2YgdGhlIHVudGFyZ2V0ZWQgZGF0YXNldHMuIEZvciB0YXJnZXRlZCBkYXRhLCB3ZSBtZXJnZSBkYXRhc2V0cyB0aGF0IGFyZSBmcm9tIHRoZSBzYW1lIHNpdGUgYW5kIHRpc3N1ZS4KClRoZW4gdGhlIHByZXByb2Nlc3Npbmcgb2YgdGhlIG5ldyBtZXJnZWQgZGF0YXNldHMgaXMgYXMgZm9sbG93czogKDEpIGxvZyB0aGUgZGF0YSAodmFsdWVzIGFyZSByYXcgaW50ZW5zaXRpZXMpLCAoMikgcmVtb3ZlIHJvd3Mgd2l0aCAkPjIwXCUkIG1pc3NpbmcgdmFsdWVzLCBhbmQgKDMpIGltcHV0ZSBtaXNzaW5nIHZhbHVlcyB1c2luZyBrbm4gKGs9MTApLgoKSW1wb3J0YW50OiBkYXRhc2V0cyB0aGF0IGRvIG5vdCBoYXZlIHVuaXF1ZSBtZXRhYm9saXRlIG5hbWVzIG9yIHNhbXBsZSBpZHMgcHJvYmFibHkgaGFkIGVycm9ycyB3aGlsZSBwYXJzaW5nIGFuZCBhcmUgdGhlcmVmb3JlIGlnbm9yZWQuIEFsc28sIHVudGFyZ2V0ZWQgd2l0aCBmYWlsZWQgbWVyZ25pbmcgb2YgdGhlIHVubmFtZWQgYW5kIG5hbWVkIHN1YnNldHMgKGUuZy4sIGR1ZSB0byBkaWZmZXJlbnQgc2FtcGxlIGlkcykgYXJlIGlnbm9yZWQgYXMgd2VsbC4KCkZpbmFsbHkgd2UgYWRkIGFuIGFkZGl0aW9uYWwgdmVyc2lvbiBmb3IgZWFjaCBkYXRhc2V0IGJ5IGRpcmVjdGx5IHJlZ3Jlc3Npbmcgb3V0IHRoZSBzYW1wbGUgb3JkZXIgY29tcG9uZW50LiBTZWUgaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDc1NzYwMy8gZm9yIG1vcmUgZGV0YWlscyAodHJlbmQgY29ycmVjdGlvbiBpcyBhbHNvIGNhbGxlZCBiYWNrZ3JvdW5kIGNvcnJlY3Rpb24sIGFuZCB0aGlzIGlzIGRvbmUgZm9yIGVhY2ggYmF0Y2ggc2VwYXJhdGVseSkuCgpgYGB7cixldmFsPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyA9IGMoKQojIHNldCBhIGJpbmFyeSB2ZWN0b3IgaW5kaWNhdGluZyB3aXRoIGVudHJpZXMgdG8gc2tpcAptZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXAgPSByZXAoRixsZW5ndGgobWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cykpCm5hbWVzKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcCkgPSBuYW1lcyhtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzKQp1bnRhcmdldGVkX21lcmdlX2Vycm9ycyA9IGxpc3QoKQpmb3IoY3Vycm5hbWUgaW4gbmFtZXMobWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cykpewogIAogIGlmKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFtjdXJybmFtZV0pe25leHR9CiAgCiAgYXJyID0gc3Ryc3BsaXQoY3Vycm5hbWUsc3BsaXQ9IiwiKVtbMV1dCiAgYXJyID0gYXJyWy1sZW5ndGgoYXJyKV0KICBuYW1lMSA9IHBhc3RlKHBhc3RlKGFycixjb2xsYXBzZT0iLCIpLCJuYW1lZCIsc2VwPSIsIikKICBuYW1lMiA9IHBhc3RlKHBhc3RlKGFycixjb2xsYXBzZT0iLCIpLCJ1bm5hbWVkIixzZXA9IiwiKQogIAogICMgSWYgZGF0YXNldCBpcyB1bnRhcmdldGVkLCBtZXJnZSBuYW1lZCBhbmQgdW5uYW1lZCwgdXBkYXRlIHRoZSBkYXRhc2V0CiAgIyBjdXJybmFtZS4gU2tpcCB0aGUgZGF0YXNldCBpZiB0aGUgbWVyZ2UgZmFpbHMsIGJ1dCBzdG9yZSBhbmQgcHJpbnQgdGhlIGVycnJvci4KICAjIElmIHRoZSBkYXRhc2V0IGlzIHRhcmdldGVkIC0gZ28gb3ZlciBhbGwgZGF0YXNldHMgd2l0aCB0aGUgc2FtZSB2aWFsIGxhYmVscyBhbmQgCiAgIyBtZXJnZSBpZiBwb3NzaWJsZS4gCiAgaWYobmFtZTIgJWluJSBuYW1lcyhtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzKSl7CiAgICBkMSA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbW25hbWUxXV0KICAgIGQyID0gbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c1tbbmFtZTJdXQogICAgbmV3ZCA9IE5VTEwKICAgIHRyeUNhdGNoKHsKICAgICAgbmV3ZCA9IG1lcmdlX25hbWVkX2FuZF91bm5hbWVkX21ldGFib2xvbWljc19kYXRhc2V0cyhkMSxkMixzdHJpY3QgPSBGKQogICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHt9KSAjIGNhbiBhZGQgZXJyb3IgaGFuZGxpbmcgaGVyZQogICAgY3Vycm5hbWUgPSBwYXN0ZShwYXN0ZShhcnIsY29sbGFwc2U9IiwiKSwidW50YXJnZXRlZCIsc2VwPSIsIikKICAgIG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFtuYW1lMV0gPSBUCiAgICBtZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXBbbmFtZTJdID0gVAogICAgaWYoaXMubnVsbChuZXdkKSl7CiAgICAgIHByaW50KHBhc3RlKCIjIFNraXBwaW5nIGRhdGFzZXQiLAogICAgICAgICAgICAgICAgICBjdXJybmFtZSwKICAgICAgICAgICAgICAgICAgImJlY2F1c2UgbWVyZ2luZyB0aGUgbmFtZWQgYW5kIHVubmFtZWQgc3Vic2V0cyBmYWlsZWQiKSkKICAgICAgbmV4dAogICAgfQogIH0KICBlbHNlewogICAgbWV0YWJvbG9taWNzX3Jhd19kYXRhc2V0c19za2lwW2N1cnJuYW1lXSA9IFQKICAgIG5ld2QgPSBtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dCiAgICBjdXJyX3ZpYWxzID0gY29sbmFtZXMobmV3ZCRzYW1wbGVfZGF0YSkKICAgIGN1cnJfc2l0ZSA9IHRvbG93ZXIodW5pcXVlKG5ld2Qkc2FtcGxlX21ldGEkc2l0ZSkpCiAgICBmb3IoYW5vdGhlcl9kYXRhc2V0IGluIG5hbWVzKG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcCkpewogICAgICBpZihtZXRhYm9sb21pY3NfcmF3X2RhdGFzZXRzX3NraXBbYW5vdGhlcl9kYXRhc2V0XSl7bmV4dH0KICAgICAgYW5vdGhlcl9kID0gbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0c1tbYW5vdGhlcl9kYXRhc2V0XV0KICAgICAgYW5vdGhlcl92aWFscyA9IGNvbG5hbWVzKGFub3RoZXJfZCRzYW1wbGVfZGF0YSkKICAgICAgYW5vdGhlcl9kX3NpdGUgPSB0b2xvd2VyKHVuaXF1ZShhbm90aGVyX2Qkc2FtcGxlX21ldGEkc2l0ZSkpCiAgICAgIGFub3RoZXJfc2hhcmVkX3ZpYWxzID0gaW50ZXJzZWN0KGFub3RoZXJfdmlhbHMsY3Vycl92aWFscykKICAgICAgaWYoY3Vycl9zaXRlID09IGFub3RoZXJfZF9zaXRlICYmCiAgICAgICAgbGVuZ3RoKGFub3RoZXJfc2hhcmVkX3ZpYWxzKT09bGVuZ3RoKGFub3RoZXJfdmlhbHMpICYmCiAgICAgICAgbGVuZ3RoKGFub3RoZXJfc2hhcmVkX3ZpYWxzKT09bGVuZ3RoKGN1cnJfdmlhbHMpCiAgICAgICl7CiAgICAgICAgICAjIGV4YW1pbmUgdGhlIG1ldGFkYXRhCiAgICAgICAgICBjdXJyX21ldGEgPSBuZXdkJHNhbXBsZV9tZXRhCiAgICAgICAgICBhbm90aGVyX21ldGEgPSBhbm90aGVyX2Qkc2FtcGxlX21ldGEKICAgICAgICAgICMgYXQgdGhpcyBwb2ludCB3ZSBrbm93IHRoYXQgYm90aCBkYXRhc2V0cyBoYXZlIHRoZQogICAgICAgICAgIyBzYW1lIHNhbXBsZXMsIGJ1dCBub3QgbmVjZXNzYXJpbHkgdGhlIHNhbWUgY29udHJvbHMKICAgICAgICAgICMgdGhlc2UgaGFzIGJlZW4gcGFydGlhbGx5IHN1Ym1pdHRlZCBhbmQgd2UgdGhlcmVmb3JlIG5lZWQgdG8KICAgICAgICAgICMgaW50ZXJzZWN0IGhlcmUKICAgICAgICAgIHNoYXJlZF9tZXRhX3NhbXBsZXMgPSBpbnRlcnNlY3Qocm93bmFtZXMoY3Vycl9tZXRhKSxyb3duYW1lcyhhbm90aGVyX21ldGEpKQogICAgICAgICAgIyBtYWtlIHN1cmUgdGhhdCB0aGUgdmlhbHMsIHR5cGVzLCBhbmQgb3JkZXIgYXJlIHRoZSBzYW1lCiAgICAgICAgICBpZihsZW5ndGgoc2hhcmVkX21ldGFfc2FtcGxlcykgPCAxIHx8CiAgICAgICAgICAgICFhbGwoY3Vycl9tZXRhW3NoYXJlZF9tZXRhX3NhbXBsZXMsMTozXT09CiAgICAgICAgICAgICAgICAgYW5vdGhlcl9tZXRhW3NoYXJlZF9tZXRhX3NhbXBsZXMsMTozXSkpewogICAgICAgICAgICBuZXh0ICMgY29udGludWUgaWYgY2Fubm90IG1lcmdlCiAgICAgICAgICB9CiAgICAgICAgICB0cnkoewogICAgICAgICAgICBuZXdkID0gbWVyZ2VfbmFtZWRfYW5kX3VubmFtZWRfbWV0YWJvbG9taWNzX2RhdGFzZXRzKAogICAgICAgICAgICAgIG5ld2QsYW5vdGhlcl9kLHN0cmljdCA9IEYpICMgbm8gc3RyaWN0IG1lcmdlIHRoYW5rcyB0byB0ZXN0IGFib3ZlCiAgICAgICAgICAgIG1ldGFib2xvbWljc19yYXdfZGF0YXNldHNfc2tpcFthbm90aGVyX2RhdGFzZXRdID0gVAogICAgICAgICAgfSkKICAgICAgfQogICAgfQogICAgCiAgICAjIHVwZGF0ZSB0aGUgbmFtZSBvZiB0aGUgZGF0YXNldAogICAgIyB3ZSBrZWVwIHRyYWNrIG9mIHRoZSBudW1iZXIgb2YgZGF0YXNldHMgZnJvbSB0aGUgZ2l2ZW4gc2l0ZSwgYXMgc29tZQogICAgIyBtZXJnZXMgbWF5IGZhaWwKICAgIGN1cnJuYW1lID0gY3Vycm5hbWUgPSBwYXN0ZShhcnJbMV0sY3Vycl9zaXRlLHNlcD0iLCIpCiAgICBudW1fY3Vycm5hbWUgPSBzdW0oZ3JlcGwoY3Vycm5hbWUsbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpKQogICAgY3Vycm5hbWUgPSBwYXN0ZShjdXJybmFtZSxudW1fY3Vycm5hbWUrMSxzZXA9IiwiKQogIH0KICAKICBwcmludChwYXN0ZSgiIyMjIyBBbmFseXppbmcgZGF0YSBmcm9tOiIsY3Vycm5hbWUpKQogIGN1cnJfZGF0YSAgPSBuZXdkJHNhbXBsZV9kYXRhCiAgY3Vycl9kYXRhMiA9IHJlY2FzdF9udW1lcmljX2RhdGFfZnJhbWUoY3Vycl9kYXRhKQogIGN1cnJfZGF0YV9tYXQgPSBhcy5tYXRyaXgoY3Vycl9kYXRhMikKICBwcmludChwYXN0ZSgibm8gZXJyb3JzIGluIHBhcnNpbmcgbnVtZXJpYyB2YWx1ZXM6IiwKICAgICAgICAgICAgICBhbGwoY3Vycl9kYXRhPT1jdXJyX2RhdGFfbWF0LG5hLnJtPVQpKSkKCiAgIyBmbG9vciB0aGUgZGF0YSBhdCAwCiAgY3Vycl9kYXRhX21hdFtjdXJyX2RhdGFfbWF0PDBdID0gMAogICMgS2VlcCBhIGxvZ2dlZCB2ZXJzaW9uIG9mIHRoZSBkYXRhCiAgY3Vycl9kYXRhX2xvZyA9IGxvZyhjdXJyX2RhdGFfbWF0KzEsYmFzZT0yKQogIAogICMgb3JnYW5pemUgdGhlIG1ldGFkYXRhCiAgY3Vycl9tZXRhID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoY3Vycl9kYXRhKSwKICAgICAgICB1bmlvbihiaW9zcGVjX2NvbHMsZGlmZmVyZW50aWFsX2FuYWx5c2lzX2NvbHMpXQogICMgcmVtb3ZlIG1ldGFkYXRhIHZhcmlhYmxlcyB3aXRoIHRvbyBtYW55IE5BcwogIG5hX2NvdW50cyA9IGFwcGx5KGlzLm5hKGN1cnJfbWV0YSksMixzdW0pCiAgY3Vycl9tZXRhID0gY3Vycl9tZXRhWyxuYV9jb3VudHMvbnJvdyhjdXJyX21ldGEpIDwgMC4xXQogIAogICMgTG9vayBhdCB0aGUgc2FtcGxlIG9yZGVyCiAgY3Vycl9vcmRlciA9IG5ld2Qkc2FtcGxlX21ldGFbcm93bmFtZXMoY3Vycl9tZXRhKSwic2FtcGxlX29yZGVyIl0KCiAgIyBvcmdhbml6ZSB0aGUgZGF0YXNldAogIGN1cnJfZGF0YSA9IGN1cnJfZGF0YVsscm93bmFtZXMoY3Vycl9tZXRhKV0KICBjdXJyX2RhdGFfbG9nID0gY3Vycl9kYXRhX2xvZ1sscm93bmFtZXMoY3Vycl9tZXRhKV0KICAjIHJlbW92ZSB6ZXJvIHZhcmlhbmNlIHJvd3Mgb3Igcm93cyB3aXRoIG1hbnkgTkFzCiAgcm93c190b19yZW0gPSAhKGFwcGx5KGN1cnJfZGF0YSwxLHNkLG5hLnJtPVQpPjApCiAgcGVyY2VudF9uYSA9IHJvd1N1bXMoaXMubmEoY3Vycl9kYXRhKSkgLyBuY29sKGN1cnJfZGF0YSkKICBwcmludChwYXN0ZSgibnVtYmVyIG9mIE5BIGNlbGxzOiIsc3VtKGlzLm5hKGN1cnJfZGF0YSkpKSkKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgcGVyY2VudF9uYSA+IDAuMgogICMgcmVtb3ZlIHJvd3Mgd2l0aCBubyBhbm5vdGF0aW9uCiAgcm93c190b19yZW0gPSByb3dzX3RvX3JlbSB8IG5ld2QkZGF0YV9yYXdfcm93bmFtZXMgPT0gIiIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgbmV3ZCRkYXRhX3Jhd19yb3duYW1lcyA9PSAiLSIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgbmV3ZCRkYXRhX3Jhd19yb3duYW1lcyA9PSAiXyIKICByb3dzX3RvX3JlbSA9IHJvd3NfdG9fcmVtIHwgaXMubmEobmV3ZCRkYXRhX3Jhd19yb3duYW1lcykKICAjIHJlbW92ZSByb3dzIHdpdGggZHVwbGljYXRlZCByZXByZXNlbnRhdGlvbgogIHJvd19kdXBsaWNhdGlvbnMgPSBuYW1lcyh3aGljaCh0YWJsZShuZXdkJGRhdGFfcmF3X3Jvd25hbWVzKT4xKSkKICBpZihhbnkobmV3ZCRkYXRhX3Jhd19yb3duYW1lc1shcm93c190b19yZW1dICVpbiUgcm93X2R1cGxpY2F0aW9ucykpewogICAgcm93c190b19yZW0gPSByb3dzX3RvX3JlbSB8IG5ld2QkZGF0YV9yYXdfcm93bmFtZXMgJWluJSByb3dfZHVwbGljYXRpb25zCiAgICBwcmludCgiIyBXQVJOSU5HOiBkYXRhc2V0IGhhcyBkdXBsaWNhdGVkIHJvdyBuYW1lcyB3aG9zZSBkYXRhIGFyZSBub3cgaWdub3JlZCIpCiAgICBwcmludChwYXN0ZShyb3dfZHVwbGljYXRpb25zLGNvbGxhcHNlPSIsIikpCiAgfQogIGN1cnJfZGF0YSA9IGN1cnJfZGF0YVshcm93c190b19yZW0sXQogIGN1cnJfZGF0YV9sb2cgPSBjdXJyX2RhdGFfbG9nWyFyb3dzX3RvX3JlbSxdCiAgIyBpbXB1dGUgLSB1c2UgY2FwdHVyZS5vdXRwdXQgdG8gYXZvaWQgcHJpbnRzCiAgY2FwdHVyZS5vdXRwdXQoCiAgICB7Y3Vycl9kYXRhX2ltcCA9IGltcHV0ZTo6aW1wdXRlLmtubihhcy5tYXRyaXgoY3Vycl9kYXRhX2xvZykpJGRhdGF9CiAgICAsZmlsZT1OVUxMKQogIGN1cnJfZGF0YV9ybmFtZXMgPSBuZXdkJGRhdGFfcmF3X3Jvd25hbWVzWyFyb3dzX3RvX3JlbV0KICByb3duYW1lcyhjdXJyX2RhdGFfaW1wKSA9IGN1cnJfZGF0YV9ybmFtZXMKICAKICAjIFJlZ3Jlc3Mgb3V0IHRoZSBzYW1wbGUgb3JkZXIKICAjIGxtX3JlZ3Jlc3NfbWF0cml4IHdvcmtzIG9uIGNvbHVtbnMKICBjdXJyX2RhdGFfaW1wMiA9IHQobG1fcmVncmVzc19vdXRfbWF0cml4KHQoY3Vycl9kYXRhX2ltcCksY3Vycl9vcmRlcikpCiAgCiAgIyB1cGRhdGUgdGhlIGRhdGEgb2JqZWN0CiAgbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSA9IG5ld2QKICBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dJHNhbXBsZV9kYXRhID0gY3Vycl9kYXRhX2ltcAogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX2RhdGFfbm9sb2dfbm9pbXAgPSBjdXJyX2RhdGEKICBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tjdXJybmFtZV1dJGRhdGFfcmF3X3Jvd25hbWVzID0gY3Vycl9kYXRhX3JuYW1lcwogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX21ldGFfcGFyc2VkID0gY3Vycl9tZXRhCiAgbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSRzYW1wbGVfZGF0YV9vcmRlcl9hZGogPSBjdXJyX2RhdGFfaW1wMgp9CgpzYXZlX3RvX2J1Y2tldChtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLAogICAgICAgICAgICAgICBmaWxlPSJtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzMTAyODIwMTkuUkRhdGEiLAogICAgICAgICAgICAgICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpCgpgYGAKCgpBbHRlcm5hdGl2ZWx5IGxvYWQgdGhlIHJlc3VsdCBmcm9tIHRoZSBidWNrZXQgdG8gc2F2ZSB0aW1lOgpgYGB7cn0KbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyA9IGxvYWRfZnJvbV9idWNrZXQoCiAgZmlsZT0ibWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0czEwMjgyMDE5LlJEYXRhIiwKICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIKKVtbMV1dCmBgYAoKIyBMb2ctdHJhbnNmb3JtOiBlZmZlY3Qgb24gZGlmZmVyZW50aWFsIGFuYWx5c2lzCgpVbnRhcmdldGVkIGRhdGEgYXJlIHR5cGljYWxseSBsb2ctdHJhbnNmb3JtZWQgYW5kIGFuYWx5emVkIHVzaW5nIGxpbmVhciBtb2RlbHMuIE9uIHRoZSBvdGhlciBoYW5kLCBjb25jZW50cmF0aW9uIGRhdGEgYXJlIHNvbWV0aW1lcyBhbmFseXplZCB3aXRoIHRoZSBzYW1lIHR5cGUgb2YgbW9kZWxzIGJ1dCB1c2luZyB0aGUgb3JpZ2luYWwgZGF0YS4gVGhpcyByYWlzZXMgYSBwcm9ibGVtIGlmIHdlIHdpc2ggdG8gY29tcGFyZSBleGFjdCBzdGF0aXN0aWNzIGZyb20gdGhlc2UgZGF0YS4gSW4gdGhpcyBzZWN0aW9uIHdlIHBlcmZvcm0gcmVzaWR1YWwgYW5hbHlzaXMgZm9yIHNpbmdsZSBtZXRhYm9saXRlcy4gT3VyIGdvYWwgaXMgdG8gaWRlbnRpZnkgaWYgY29uY2VudHJhdGlvbiBkYXRhIGJlaGF2ZXMgIm5vcm1hbGx5IiB3aGVuIG5vdCBsb2ctdHJhbnNmb3JtZWQuIFRoZSBhbmFseXNpcyBiZWxvdyBleGFtaW5lcyB0aGUgcmVzaWR1YWxzIG9mIHRoZSBkYXRhIGFmdGVyIGZpdHRpbmcgbGluZWFyIG1vZGVscyBmb3IgZWFjaCBtZXRhYm9saXRlLCBhZGp1c3RpbmcgZm9yIGZyZWV6ZSB0aW1lIGFuZCBzZXguIFdlIHRoZW4gY29tcGFyZSB0aGUgcmVzdWx0cyB3aXRoIGFuZCB3aXRob3V0IHRoZSBsb2ctdHJhbnNmb3JtYXRpb24sIGNvdW50aW5nIHRoZSBudW1iZXIgb2YgbWV0YWJvbGl0ZXMgd2l0aCBhIHNpZ25pZmljYW50IGV2aWRlbmNlIGZvciBub24tbm9ybWFsbHkgZGlzdHJpYnV0ZWQgcmVzaWR1YWxzLiAKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KIyBjaGVjayBmb3Igbm9ybWFsaXR5IHVzaW5nIHRoZSBLb2xtb2dvcm92LVNtaXJub3YgdGVzdAppc19ub3JtYWxfdGVzdDwtZnVuY3Rpb24odixzYW1wPTEwMDAwKXsKICByZXR1cm4oa3MudGVzdCh2LCJwbm9ybSIsbWVhbih2LG5hLnJtPVQpLHNkKHYsbmEucm0gPSBUKSkkcC52YWx1ZSkKfQojIGdvIG92ZXIgdGhlIG5hbWVkIGRhdGFzZXRzLCBnZXQgYSBsb2dnZWQgYW5kIGFuIHVubG9nZ2VkIHZlcnNpb24gb2YKIyB0aGUgZGF0YSwgdXNlIHRoZXNlIGFzIGlucHV0cyBmb3IgdGhlIHJlZ3Jlc3Npb24KcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBpZihncmVwbCgidW50YXJnZXRlZCIsbm4xKSl7bmV4dH0KICB4X2xvZyA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9kYXRhCiAgeF91bmxvZyA9IDJeeF9sb2cKICAKICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMsIGlnbm9yZSBkaXN0YW5jZXMKICB4X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogIGN1cnJfY292cyA9IHhfbWV0YVssaW50ZXJzZWN0KGNvbG5hbWVzKHhfbWV0YSksYmlvc3BlY19jb2xzWzJdKV0KICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKGN1cnJfY292cywKICAgICAgICAgICBzZXg9eF9tZXRhJGFuaW1hbC5yZWdpc3RyYXRpb24uc2V4KQogIAogICMgZ2V0IHRoZSBsbSBvYmplY3RzCiAgY3Vycl9tb2RlbHMgPSBsaXN0KCkKICBmb3IodHAgaW4gdW5pcXVlKHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICByZXNfbG9nID0gYXBwbHkoCiAgICAgICAgeF9sb2csMSwKICAgICAgICBwYXNzMWFfc2ltcGxlX2RpZmZlcmVudGlhbF9hYnVuZGFuY2UsCiAgICAgICAgdHBzID0geF9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgIGlzX2NvbnRyb2wgPSB4X21ldGEkYW5pbWFsLmtleS5pc19jb250cm9sLAogICAgICAgIGNvdnMgPSBjdXJyX2NvdnMscmV0dXJuX21vZGVsPVQKICAgICAgKQogICAgICByZXNfdW5sb2cgPSBhcHBseSgKICAgICAgICB4X3VubG9nLDEsCiAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgIHRwcyA9IHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCx0cD10cCwKICAgICAgICBpc19jb250cm9sID0geF9tZXRhJGFuaW1hbC5rZXkuaXNfY29udHJvbCwKICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1UCiAgICAgICkKICAgICAgaXNfbm9ybSA9IGNiaW5kKAogICAgICAgIHNhcHBseShyZXNfbG9nLGZ1bmN0aW9uKHgpaXNfbm9ybWFsX3Rlc3QocmVzaWR1YWxzKHgpKSksCiAgICAgICAgc2FwcGx5KHJlc191bmxvZyxmdW5jdGlvbih4KWlzX25vcm1hbF90ZXN0KHJlc2lkdWFscyh4KSkpCiAgICAgICkKICAgICAgY29sbmFtZXMoaXNfbm9ybSkgPSBjKCJsb2ciLCJub3QgbG9nIikKICAgICAgY3Vycl9tb2RlbHNbW2FzLmNoYXJhY3Rlcih0cCldXSA9IGlzX25vcm0KICB9CiAgcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0c1tbbm4xXV0gPSBjdXJyX21vZGVscwp9CgojIElzIHRoZXJlIGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gb3B0aW9ucz8KbG9nX3ZzX3VubG9nX3N1bW1fbWF0ID0gc2FwcGx5KHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICBmdW5jdGlvbih4KXNhcHBseSh4LAogICAgICAgIGZ1bmN0aW9uKHkpCiAgICAgICAgICB3aWxjb3gudGVzdCh5WywxXSx5WywyXSxwYWlyZWQgPSBULGFsdGVybmF0aXZlID0gImciKSRwLnZhbHVlKSkKCiMgQ291bnQgdGhlIG51bWJlciBvZiBub24tbm9ybWFsIG1ldGFib2xpdGVzCm51bV9ub25ub3JtYWxfbG9nID0gc2FwcGx5KHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICBmdW5jdGlvbih4KXNhcHBseSh4LAogICAgICAgIGZ1bmN0aW9uKHkpc3VtKHlbLDFdPDAuMDUpKSkKbnVtX25vbm5vcm1hbF9sb2cgPSAKICBudW1fbm9ubm9ybWFsX2xvZ1ssb3JkZXIoY29sbmFtZXMobnVtX25vbm5vcm1hbF9sb2cpKV0KbnVtX25vbm5vcm1hbF91bmxvZyA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KXN1bSh5WywyXTwwLjA1KSkpCm51bV9ub25ub3JtYWxfdW5sb2cgPSAKICBudW1fbm9ubm9ybWFsX3VubG9nWyxvcmRlcihjb2xuYW1lcyhudW1fbm9ubm9ybWFsX3VubG9nKSldCgpsaWJyYXJ5KGNvcnJwbG90KQpwYXIobWFyID0gYyg1LDUsNSwxMCkpCmNvcnJwbG90KHQobnVtX25vbm5vcm1hbF9sb2cpLSB0KG51bV9ub25ub3JtYWxfdW5sb2cpLAogICAgICAgICBpcy5jb3JyID0gRix0bC5jZXggPSAwLjcpCgoKYGBgCgoKIyBUcmFnZXRlZCB2cy4gVW50YXJnZXRlZDogc2luZ2xlIG1ldGFib2xpdGUgY29tcGFyaXNvbgoKIyMgQ29tcHV0ZSBzdGF0aXN0aWNzIGZvciBlYWNoIGRhdGFzZXQKCkNvbXBhcmUgb3ZlcmxhcHMsIGVmZmVjdCBzaXplcywgYW5kIGNvcnJlbGF0aW9ucyB3aXRoaW4gdGlzc3Vlcy4gQ29tcGFyZSB0YXJnZXRlZC11bnRhcmdldGVkIHBhaXJzIG9ubHkuIEZvciBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgd2UgdXNlIHRoZSBzYW1lIG1vZGVsIGFzIGluIHRoZSBhbmFseXNpcyBhYm92ZS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCiMgaGVscGVyIGZ1bmN0aW9uIHRvIHRyYW5zZm9ybSBhIG1ldGFib2xvbWljcyBtYXRyaXgKIyB0byB0aGF0IG9mIGl0cyBtb3RycGFjIGNvbXBvdW5kIGlkcwpleHRyYWN0X21ldGFiX2RhdGFfZnJvbV9yb3dfYW5ub3Q8LWZ1bmN0aW9uKHgscm93X2Fubm90X3gpewogICMgZ2V0IHRoZSBjb2xvdW1uIHRoYXQgaGFzIHRoZSByb3cgbmFtZXMKICBpbnRfc2l6ZXMgPSBhcHBseShyb3dfYW5ub3RfeCwyLGZ1bmN0aW9uKHgseSlsZW5ndGgoaW50ZXJzZWN0KHgseSkpLHk9cm93bmFtZXMoeCkpCiAgaW5kID0gd2hpY2goaW50X3NpemVzPT1tYXgoaW50X3NpemVzLG5hLnJtID0gVCkpWzFdCiAgcm93X2Fubm90X3ggPSByb3dfYW5ub3RfeFtpcy5lbGVtZW50KHJvd19hbm5vdF94WyxpbmRdLHNldD1yb3duYW1lcyh4KSksXQogIHJvd25hbWVzKHJvd19hbm5vdF94KSA9IHJvd19hbm5vdF94WyxpbmRdCiAgc2hhcmVkID0gaW50ZXJzZWN0KHJvd25hbWVzKHJvd19hbm5vdF94KSxyb3duYW1lcyh4KSkKICB4ID0geFtzaGFyZWQsXQogIHJvd19hbm5vdF94ID0gcm93X2Fubm90X3hbc2hhcmVkLF0KICByb3duYW1lcyh4KSA9IHJvd19hbm5vdF94JG1vdHJwYWNfY29tcF9uYW1lCiAgcmV0dXJuKHgpCn0KCnNpbmdsZV9tZXRhYm9saXRlX2NvcnJzID0gbGlzdCgpCnNpbmdsZV9tZXRhYm9saXRlX2RlID0gYygpCm5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgc2luZ2xlX21ldGFib2xpdGVfY29ycnNbW25uMV1dID0gbGlzdCgpCiAgbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXNbW25uMV1dID0gTlVMTAogIGZvcihubjIgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogICAgaWYobm4yID09IG5uMSl7bmV4dH0KICAgIGlmKCFncmVwbCgidW50YXJnZXRlZCIsbm4yKSl7bmV4dH0KICAgIG5uMl90aXNzdWUgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgICBubjJfdGlzc3VlID0gZ3N1YigiX3Bvd2RlciIsIiIsbm4yX3Rpc3N1ZSkKICAgIG5uMl9kYXRhc2V0ID0gc3Ryc3BsaXQobm4yLHNwbGl0PSIsIilbWzFdXVsyXQogICAgaWYobm4xX3Rpc3N1ZSE9bm4yX3Rpc3N1ZSl7bmV4dH0KICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB4ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX2RhdGEKICAgIHkgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRzYW1wbGVfZGF0YQogICAgcm93X2Fubm90X3ggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRyb3dfYW5ub3QKICAgIHJvd19hbm5vdF95ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4yXV0kcm93X2Fubm90CiAgICAjIHRyYW5zZm9ybSBtZXRhYm9saXRlIG5hbWVzIHRvIHRoZSBtb3RycGFjIGNvbXAgbmFtZQogICAgeCA9IGV4dHJhY3RfbWV0YWJfZGF0YV9mcm9tX3Jvd19hbm5vdCh4LHJvd19hbm5vdF94KQogICAgeSA9IGV4dHJhY3RfbWV0YWJfZGF0YV9mcm9tX3Jvd19hbm5vdCh5LHJvd19hbm5vdF95KQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IGFzLm1hdHJpeCh4WyxzaGFyZWRfYmlkc10pCiAgICB5ID0gYXMubWF0cml4KHlbLHNoYXJlZF9iaWRzXSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgZ2V0IHRoZSBzaGFyZWQgbWF0ZWJvbGl0ZXMKICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IGludGVyc2VjdChyb3duYW1lcyh4KSxyb3duYW1lcyh5KSkKICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IG5hLm9taXQoc2hhcmVkX21ldGFib2xpdGVzKQogICAgaWYobGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcyk9PTApe25leHR9CiAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0gPSB1bmlvbigKICAgICAgbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXNbW25uMV1dLAogICAgICBzaGFyZWRfbWV0YWJvbGl0ZXMKICAgICkKICAgIAogICAgIyBDb21wdXRlIHRoZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBvZiB0aGUgc2hhcmVkIG1ldGFib2xpdGVzCiAgICBpZihsZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKT4xKXsKICAgICAgICAgIGNvcnJzID1jb3IodCh4W3NoYXJlZF9tZXRhYm9saXRlcyxdKSwKICAgICAgICAgICAgICAgIHQoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSksbWV0aG9kID0gInNwZWFybWFuIikKICAgIH0KICAgIGVsc2V7CiAgICAgICAgICBjb3JycyA9IGNvcih4W3NoYXJlZF9tZXRhYm9saXRlcyxdLAogICAgICAgICAgICAgICAgeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxtZXRob2QgPSAic3BlYXJtYW4iKQogICAgfQogICAgCiAgICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMgKGlnbm9yZSBkaXN0YW5jZXMpCiAgICBjdXJyX2Nvdl9jb2xzID0gaW50ZXJzZWN0KGNvbG5hbWVzKHlfbWV0YSksYmlvc3BlY19jb2xzWzJdKQogICAgY3Vycl9jb3ZzID0gZGF0YS5mcmFtZSh5X21ldGFbLGN1cnJfY292X2NvbHNdKQogICAgbmFtZXMoY3Vycl9jb3ZzKSA9IGN1cnJfY292X2NvbHMKICAgIGN1cnJfY292cyRzZXggPSB5X21ldGEkYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXggIyBhZGQgc2V4CiAgICAKICAgICMgZGlmZmVyZW50aWFsIGFuYWx5c2lzCiAgICBmb3IodHAgaW4gdW5pcXVlKHlfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICByZXN4ID0gdChhcHBseSgKICAgICAgICBtYXRyaXgoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB5X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9RgogICAgICApKQogICAgICByZXN5ID0gdChhcHBseSgKICAgICAgICBtYXRyaXgoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB5X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9RgogICAgICApKQogICAgICAjIEFkZCBkYXRhc2V0IGluZm9ybWF0aW9uLCB0aW1lIHBvaW50LCB0aXNzdWUKICAgICAgIyBUaGVzZSBhcmUgaW1wb3J0YW50IGFubm90YXRpb25zIGZvciBvdXIgc3VtbWFyeSBtYXRyaXgKICAgICAgIyBjYWxsZWQgc2luZ2xlX21ldGFib2xpdGVfZGUgYmVsb3cKICAgICAgYWRkZWRfY29sdW1ucyA9IG1hdHJpeChjYmluZCgKICAgICAgICByZXAobm4xLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICByZXAobm4yLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICBzaGFyZWRfbWV0YWJvbGl0ZXMsCiAgICAgICAgcmVwKHRwLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICByZXAobm4xX3Rpc3N1ZSxsZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKSkKICAgICAgKSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKQogICAgICByZXN4ID0gY2JpbmQocmVzeCxyZXAoVCxucm93KHJlc3gpKSkKICAgICAgY29sbmFtZXMocmVzeClbbmNvbChyZXN4KV0gPSAiaXNfdGFyZ2V0ZWQiCiAgICAgIHJlc3kgPSBjYmluZChyZXN5LHJlcChGLG5yb3cocmVzeSkpKQogICAgICBjb2xuYW1lcyhyZXN5KVtuY29sKHJlc3kpXSA9ICJpc190YXJnZXRlZCIKICAgICAgaWYobnJvdyhyZXN4KT4xKXsKICAgICAgICByZXN4ID0gY2JpbmQoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgcmVzeSA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0xXSxyZXN5KQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgcmVzeCA9IGMoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgcmVzeSA9IGMoYWRkZWRfY29sdW1uc1ssLTFdLHJlc3kpCiAgICAgIH0KICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSByYmluZChzaW5nbGVfbWV0YWJvbGl0ZV9kZSxyZXN4KQogICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHJiaW5kKHNpbmdsZV9tZXRhYm9saXRlX2RlLHJlc3kpCiAgICB9CiAgICAKICAgIHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1tubjFdXVtbbm4yXV0gPSBjb3JycwogIH0KfQoKIyBSZWZvcm1hdCB0aGUgcmVzdWx0cyBmb3IgZWFzaWVyIGNvbXBhcmlzb24gbGF0ZXIKc2luZ2xlX21ldGFib2xpdGVfZGUgPSBkYXRhLmZyYW1lKHNpbmdsZV9tZXRhYm9saXRlX2RlKQpuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkgPSBjKAogICJkYXRhc2V0IiwibWV0YWJvbGl0ZSIsInRwIiwidGlzc3VlIiwKICAiRXN0IiwiU3RkIiwiVHN0YXQiLCJQdmFsdWUiLCJpc190YXJnZXRlZCIpCmZvcihjb2wgaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpWy1jKDE6NCldKXsKICBzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbY29sXV0gPSBhcy5udW1lcmljKAogICAgYXMuY2hhcmFjdGVyKHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSkpCn0KZm9yKGNvbCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSlbMTo0XSl7CiAgc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dID0gCiAgICBhcy5jaGFyYWN0ZXIoc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dKQp9CiMgUmVtb3ZlIGR1cGxpY2F0aW9ucwpyb3duYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkgPSBOVUxMCmZvcihubiBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9kZSkpewogIG5kaWcgPSA1CiAgaWYoZ3JlcGwoInB2YWwiLG5uLGlnbm9yZS5jYXNlID0gVCkpewogICAgbmRpZyA9IDEwCiAgfQogIGlmKGlzLm51bWVyaWMoc2luZ2xlX21ldGFib2xpdGVfZGVbW25uXV0pKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tubl1dID0gcm91bmQoc2luZ2xlX21ldGFib2xpdGVfZGVbW25uXV0sZGlnaXRzID0gbmRpZykKICB9Cn0Kc2luZ2xlX21ldGFib2xpdGVfZGUgPSB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUpCgpgYGAKCldlIG5leHQgdHJhbnNmb3JtIHRoZSBkYXRhIGFib3ZlIGludG8gdGFibGVzIHRoYXQgY29udGFpbiBkYXRhIGZvciBlYWNoIGNvbWJpbmF0aW9uIG9mIG1ldGFib2xpdGUsIHRpbWUgcG9pbnQsIGFuZCB0aXNzdWUuIFRoZXNlIGFyZSB0aGVuIHVzZWQgZm9yIGRpZmZlcmVudCBtZXRhLWFuYWx5c2VzOiAoMSkgYSBzaW1wbGUgcmFuZG9tIGVmZmVjdHMgYW5hbHlzaXMsICgyKSByYW5kb20gZWZmZWN0cyB3aXRoIGEgYmluYXJ5IGNvdmFyaWF0ZSBpbmRpY2F0aW5nIGlmIGEgZGF0YXNldCBpcyB0YXJnZXRlZCBvciB1bnRhcmdldGVkLCAoMykgcmVkbyB0aGUgUkUgbW9kZWwgb2YgKDEpIHdpdGggdGhlIHRhcmdldGVkIGRhdGEgb25seSwgYW5kICg0KSByZWRvIHRoZSBSRSBtb2RlbCBvZiAoMSkgd2l0aCB0aGUgdW50YXJnZXRlZCBkYXRhIG9ubHkuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CgpsaWJyYXJ5KG1ldGFmb3IpCm1ldGFfYW5hbHlzaXNfc3RhdHMgPSBsaXN0KCkKZm9yKHRpc3N1ZSBpbiB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUkdGlzc3VlKSl7CiAgZm9yKHRwIGluIHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0cCkpewogICAgY3Vycl9zdWJzZXQgPSBzaW5nbGVfbWV0YWJvbGl0ZV9kZVsKICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUkdGlzc3VlPT10aXNzdWUgJgogICAgICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlJHRwPT10cCxdCiAgICBmb3IobWV0YWJvbGl0ZSBpbiB1bmlxdWUoY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZSkpewogICAgICBjdXJyX21ldF9kYXRhID0gY3Vycl9zdWJzZXRbCiAgICAgICAgY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZT09bWV0YWJvbGl0ZSxdCiAgICAgIGN1cnJfbWV0X2RhdGEkdmFyID0gY3Vycl9tZXRfZGF0YSRTdGReMgogICAgICByZV9tb2RlbDEgPSBOVUxMO3JlX21vZGVsMj1OVUxMCiAgICAgIHJlX21vZGVsX3RhciA9IE5VTEw7cmVfbW9kZWxfdW50YXIgPSBOVUxMCiAgICAgIHRyeSh7cmVfbW9kZWwxID0gcm1hLnVuaShjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhcil9KQogICAgICB0cnkoe3JlX21vZGVsMiA9IHJtYS5tdihjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhciwKICAgICAgICAgICAgICAgICAgICAgICAgIG1vZHM9Y3Vycl9tZXRfZGF0YSRpc190YXJnZXRlZCl9KQogICAgICB0cnkoe3JlX21vZGVsX3RhciA9IHJtYS51bmkoCiAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0xLCJFc3QiXSwKICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTEsInZhciJdCiAgICAgICl9KQogICAgICB0cnkoe3JlX21vZGVsX3VudGFyID0gcm1hLnVuaSgKICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTAsIkVzdCJdLAogICAgICAgIGN1cnJfbWV0X2RhdGFbY3Vycl9tZXRfZGF0YSRpc190YXJnZXRlZD09MCwidmFyIl0KICAgICAgKX0pCiAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW3Bhc3RlKG1ldGFib2xpdGUsdGlzc3VlLHRwLHNlcD0iLCIpXV0gPSAKICAgICAgICBsaXN0KGN1cnJfbWV0X2RhdGE9Y3Vycl9tZXRfZGF0YSxyZV9tb2RlbDE9cmVfbW9kZWwxLAogICAgICAgICAgICByZV9tb2RlbDIgPSByZV9tb2RlbDIscmVfbW9kZWxfdGFyPXJlX21vZGVsX3RhciwKICAgICAgICAgICAgcmVfbW9kZWxfdW50YXIgPSByZV9tb2RlbF91bnRhcikKICAgIH0KICB9Cn0KCgpgYGAKCiMjIENvbXBhcmlzb24gcmVzdWx0cwoKV2Ugbm93IHNob3cgc29tZSBwbG90cyB0byBzdW1tYXJpemUgdGhlIGNvbXBhcmlzb24uIAoKIyMjIERhdGFzZXQgY292ZXJhZ2UgCgpXZSBmaXJzdCBwbG90IHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2Ugb2YgbWV0YWJvbGl0ZXMgaW4gdGhlIHRhcmdldGVkIGRhdGFzZXRzIHRoYXQgYXJlIG1lYXN1cmVkIGluIGF0IGxlYXN0IG9uZSB1bnRhcmdldGVkIGRhdGFzZXQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKZGF0YXNldDJudW1fbWV0YWJvbGl0ZXMgPSBzYXBwbHkobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpbnJvdyh4JHNhbXBsZV9kYXRhKSkKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IHNhcHBseShuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlcyxsZW5ndGgpCm5hbWVkX2RhdGFzZXRfY292ZXJhZ2UgPSBkYXRhLmZyYW1lKAogIG5hbWUgPSBuYW1lcyhuYW1lZF9kYXRhc2V0X2NvdmVyYWdlKSwKICBwZXJjZW50YWdlID0gbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSAvCiAgZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbbmFtZXMobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSldLAogIGNvdW50ID0gbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwKICB0b3RhbCA9IGRhdGFzZXQybnVtX21ldGFib2xpdGVzW25hbWVzKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UpXQopCiMgYWRkIGRhdGFzZXRzIHdpdGggbm8gY292ZXJhZ2UKYWxsX3RhcmdldGVkX2RhdGFzZXRzID0gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykKYWxsX3RhcmdldGVkX2RhdGFzZXRzID0gYWxsX3RhcmdldGVkX2RhdGFzZXRzWyFncmVwbCgidW50YXJnIixhbGxfdGFyZ2V0ZWRfZGF0YXNldHMpXQp6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzID0gc2V0ZGlmZihhbGxfdGFyZ2V0ZWRfZGF0YXNldHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UkbmFtZSkKemVyb19jb3ZlcmFnZV9kYXRhc2V0cyA9IGRhdGEuZnJhbWUoCiAgbmFtZSA9IHplcm9fY292ZXJhZ2VfZGF0YXNldHMsCiAgcGVyY2VudGFnZSA9IHJlcCgwLGxlbmd0aCh6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzKSksCiAgY291bnQgPSByZXAoMCxsZW5ndGgoemVyb19jb3ZlcmFnZV9kYXRhc2V0cykpLAogIHRvdGFsID0gZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbemVyb19jb3ZlcmFnZV9kYXRhc2V0c10KKQpuYW1lZF9kYXRhc2V0X2NvdmVyYWdlID0gcmJpbmQobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgemVyb19jb3ZlcmFnZV9kYXRhc2V0cykKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IAogIG5hbWVkX2RhdGFzZXRfY292ZXJhZ2Vbb3JkZXIoYXMuY2hhcmFjdGVyKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UkbmFtZSkpLF0KcHJpbnQoZ2dwbG90KG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsIGFlcyh4PW5hbWUsIHk9cGVyY2VudGFnZSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsd2lkdGg9MC4yKSArIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV90ZXh0KGRhdGE9bmFtZWRfZGF0YXNldF9jb3ZlcmFnZSwgCiAgICAgICAgICAgIGFlcyhuYW1lLCBwZXJjZW50YWdlKzAuMDUsIGxhYmVsPWNvdW50KSwgCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGg9MC45KSwKICAgICAgICAgICAgc2l6ZT00KSArIAogIGdndGl0bGUoIlRhcmdldGVkIGRhdGFzZXQ6IGNvdmVyYWdlIGJ5IHVudGFyZ2V0ZWQiKSkKCmBgYAoKIyMjIFNwZWFybWFuIGNvcnJlbGF0aW9ucwoKV2UgZXhhbWluZSB0aGUgYXZlcmFnZSBhYnNvbHV0ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBwbGF0Zm9ybXMgKHdpdGhpbiB0aXNzdWVzKS4gV2hlbmV2ZXIgdHdvIHBsYXRmb3JtcyBzaGFyZSBtb3JlIHRoYW4gYSBzaW5nbGUgbWV0YWJvbGl0ZSB3ZSBwbG90IGJvdGggdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgc2FtZSBtZXRhYm9saXRlcyBhbmQgYmV0d2VlbiBvdGhlciBtZXRhYm9saXRlcy4gQWRkaW5nIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIGJldHdlZW4gcGxhdGZvcm1zIGJ1dCB3aXRoIGRpZmZlcmVudCBtZXRhYm9saXRlcyBpcyBpbXBvcnRhbnQgYXMgaXQgZ2l2ZXMgc29tZSBwZXJzcGVjdGl2ZSB0byB3aGF0IGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gaXMuIFRoYXQgaXMsIGluIG1hbnkgY2FzZXMgYmVsb3csIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIG1heSBiZSBncmVhdGVyIHRoYW4gZXhwZWN0ZWQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgTmV4dCBleGFtaW5lIHRoZSBTcGVhcm1hbiBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwbGF0Zm9ybXMKbWVhbl9hYnM8LWZ1bmN0aW9uKHgsLi4uKXtyZXR1cm4obWVhbihhYnMoeCksLi4uKSl9CnNkX2FiczwtZnVuY3Rpb24oeCwuLi4pe3JldHVybihzZChhYnMoeCksLi4uKSl9CmV4dHJhY3RfZGlhZ192c19ub25fZGlhZzwtZnVuY3Rpb24oY29ycnMsZnVuYz1tZWFuX2FicywuLi4pewogIGlmKGxlbmd0aChjb3Jycyk9PTEpewogICAgcmV0dXJuKGMoc2FtZT1mdW5jKGNvcnJzLC4uLiksb3RoZXI9TkEpKQogIH0KICBzYW1lID0gZnVuYyhkaWFnKGNvcnJzKSwuLi4pCiAgb3RoZXIgPSBmdW5jKAogICAgYyhjb3Jyc1tsb3dlci50cmkoY29ycnMsZGlhZyA9IEYpXSksLi4uKQogIHJldHVybihjKHNhbWU9c2FtZSxvdGhlcj1vdGhlcikpCn0KCmZvcih0YXJfZGF0YXNldCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycykpewogIGwgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbdGFyX2RhdGFzZXRdXQogIGlmKGxlbmd0aChsKT09MCl7bmV4dH0KICBjb3JyX2luZm8gPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZykpKQogIGNvcnJfc2QgPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZyxmdW5jPXNkX2FicykpKQogIHJvd25hbWVzKGNvcnJfaW5mbykgPSBzYXBwbHkocm93bmFtZXMoY29ycl9pbmZvKSwKICAgICAgZnVuY3Rpb24oeClzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVsyXSkKICByb3duYW1lcyhjb3JyX2luZm8pID0gZ3N1YigibWV0YWJfdV8iLCIiLHJvd25hbWVzKGNvcnJfaW5mbykpCiAgcm93bmFtZXMoY29ycl9zZCkgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9pbmZvJGRhdGFzZXQgPSByb3duYW1lcyhjb3JyX2luZm8pCiAgY29ycl9zZCRkYXRhc2V0ID0gY29ycl9pbmZvJGRhdGFzZXQKICBjb3JyX2luZm8gPSBtZWx0KGNvcnJfaW5mbykKICBjb3JyX3NkID0gbWVsdChjb3JyX3NkKQogIGNvcnJfaW5mbyRzZCA9IGNvcnJfc2QkdmFsdWUKICBwcmludCgKICAgIGdncGxvdChjb3JyX2luZm8sIGFlcyh4PWRhdGFzZXQsIHk9dmFsdWUsIGZpbGw9dmFyaWFibGUpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj12YWx1ZS1zZCwgeW1heD12YWx1ZStzZCksbmEucm09VCwgCiAgICAgICAgICAgICAgICAgICB3aWR0aD0uMixwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSguOSkpICsKICAgIGdndGl0bGUodGFyX2RhdGFzZXQpICsgeGxhYigiVW50YXJnZXRlZCBkYXRhc2V0IikgKyB5bGFiKCJTcGVhcm1hbiIpICsKICAgICAgbGFicyhmaWxsID0gIlBhaXIgdHlwZSIpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIixsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKQogICkKfQpgYGAKCiMjIyBNZXRhLWFuYWx5c2lzIG9mIGRpZmZlcmVudGlhbCBlZmZlY3RzCgpIZXJlIHdlIGdvIGludG8gdGhlIHNpbmdsZSBtZXRhYm9saXRlcyBjb21wYXJpc29uIGluIGdyZWF0ZXIgZGV0YWlsIChhZ2Fpbiwgd2l0aGluIHRpc3N1ZXMpLiAKCldlIHN0YXJ0IHdpdGggYSBmZXcgZXhhbXBsZXMuIEhlcmUgYXJlIHRoZSByZXN1bHRzIGZvciBsYWN0YXRlIGluIHBsYXNtYS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGFjdF9yZXMgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogIGdyZXBsKCJsYWN0IixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpICYKICAgIGdyZXBsKCJwbGFzbWEiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkKXQpsYWN0X3Jlc19ob3VycyA9IHNhcHBseShuYW1lcyhsYWN0X3JlcyksCiAgICAgICAgICAgIGZ1bmN0aW9uKHgpYXMubnVtZXJpYyhzdHJzcGxpdCh4LHNwbGl0PSIsIilbWzFdXVszXSkpCmxhY3RfcmVzID0gbGFjdF9yZXNbb3JkZXIobGFjdF9yZXNfaG91cnMpXQpmb3IobGFjdF9leGFtcGxlIGluIG5hbWVzKGxhY3RfcmVzKVsxOjZdKXsKICBmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV0kcmVfbW9kZWwxLAogICAgICAgc2xhYiA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dW1sxXV1bLDFdLAogICAgICAgbWFpbiA9IGxhY3RfZXhhbXBsZSx4bGFiID0gIkxvZyBmYyIsCiAgICAgICBjb2wgPSAiYmx1ZSIsY2V4ID0gMS4xKQp9CgpgYGAKCldlIGNhbiBub3cgY2hlY2sgdGhlIHNhbWUgYW5hbHlzaXMgZm9yIGxpdmVyOgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsYWN0X3JlcyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgZ3JlcGwoImxhY3QiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkgJgogICAgZ3JlcGwoImxpdmVyIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXVtbMV1dWywxXSwKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgpXZSBub3cgbW92ZSB0byBhIHN5c3RlbWF0aWMgY29tcGFyaXNvbiBvZiB0aGUgZGF0YXNldHMuIFdlIHN0YXJ0IHdpdGggYSBuYWl2ZSBjb21wYXJpc29uLCBjaGVja2luZyBpZiBtZXRhYm9saXRlcyBhcmUgY29uc2lzdGVudGx5IGJlaW5nIGRpc2NvdmVyZWQgdXNpbmcgYSBzaW1wbGUgJHA9MC4wMDEkIHRocmVzaG9sZCBmb3Igc2lnbmlmaWNhbmNlLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQojIE5haXZlIGNvbXBhcmlzb24KdGhyID0gMC4wMDEKbmFpdmVfYW5hbHlzaXNfdGFibGVzID0gbGFwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsCiAgICBmdW5jdGlvbih4KXRhYmxlKHgkY3Vycl9tZXRfZGF0YVssIlB2YWx1ZSJdPHRociwKICAgICAgICAgICAgICAgICAgICAgeCRjdXJyX21ldF9kYXRhWywiaXNfdGFyZ2V0ZWQiXSkpCnRhYmxlX3dpdGhfc2lnX3Jlc3VsdHMgPSBuYWl2ZV9hbmFseXNpc190YWJsZXNbCiAgc2FwcGx5KG5haXZlX2FuYWx5c2lzX3RhYmxlcyxucm93KT4xCl0KcGVyY2VudF93aXRoX3NpZyA9IAogIGxlbmd0aCh0YWJsZV93aXRoX3NpZ19yZXN1bHRzKS9sZW5ndGgobmFpdmVfYW5hbHlzaXNfdGFibGVzKQoKc2lnX3Jlc3VsdHMgPSBzYXBwbHkodGFibGVfd2l0aF9zaWdfcmVzdWx0cywKICAgICAgZnVuY3Rpb24oeClwYXN0ZSh4WyJUUlVFIixjKCIwIiwiMSIpXT4wLGNvbGxhcHNlPSIsIikpCnNpZ19yZXN1bHRzX2NvdW50cyA9IHRhYmxlKHNpZ19yZXN1bHRzKQoKcHJpbnQoIkNvdW50aW5nIHRoZSBudW1iZXIgb2YgbWV0YWJvbGl0ZXMgd2l0aCBwPDAuMDAxIikKcHJpbnQocGFzdGUoIlNpZ25pZmljYW50IGluIHRhcmdldGVkIG9ubHk6IixzaWdfcmVzdWx0c19jb3VudHNbIkZBTFNFLFRSVUUiXSkpCnByaW50KHBhc3RlKCJTaWduaWZpY2FudCBpbiB1bnRhcmdldGVkIG9ubHk6IixzaWdfcmVzdWx0c19jb3VudHNbIlRSVUUsRkFMU0UiXSkpCnByaW50KHBhc3RlKCJTaWduaWZpY2FudCBpbiBib3RoOiIsc2lnX3Jlc3VsdHNfY291bnRzWyJUUlVFLFRSVUUiXSkpCgpgYGAKCkFzIGEgbW9yZSByaWdvcm91cyBhbmFseXNpcywgd2UgdXNlIHRoZSBtZXRhLWFuYWx5c2VzIHRvIG9idGFpbiB1c2VmdWwgc3RhdGlzdGljcyBmb3IgY29tcGFyaXNvbi4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KIyBFeHRyYWN0IHNvbWUgdXNlZnVsIHN0YXRpc3RpY3MgcGVyIGFuYWx5c2lzCgojIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKdGFyZ2V0ZWRfZGlmZl9wID0gCiAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCnB2YWxzX3RhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkcHZhbCkKcHZhbHNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkcHZhbCkKcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCnNpZ25pZmljYW50X2luID0gcmVwKCJOb25lIixsZW5ndGgocHZhbHNfdW50YXIpKQpzaWduaWZpY2FudF9pbltwdmFsc190YXI8MC4wMDFdID0gIlRhcmdldGVkIgpzaWduaWZpY2FudF9pbltwdmFsc191bnRhcjwwLjAwMV0gPSAiVW50YXJnZXRlZCIKc2lnbmlmaWNhbnRfaW5bcHZhbHNfdGFyPDAuMDAxICYgcHZhbHNfdW50YXI8MC4wMDFdID0gIkJvdGgiCnNpZ25pZmljYW50X2RpZmYgPSB0YXJnZXRlZF9kaWZmX3A8MC4wMDEKZGYgPSBkYXRhLmZyYW1lKAogIHRhcmdldGVkID0gLWxvZzEwKHB2YWxzX3RhciksCiAgdW50YXJnZXRlZCA9IC1sb2cxMChwdmFsc191bnRhciksCiAgc2lnbmlmaWNhbnRfaW4gPSBzaWduaWZpY2FudF9pbiwKICBzaWduaWZpY2FudF9kaWZmID0gc2lnbmlmaWNhbnRfZGlmZgopCnJobyA9IGNvcihwdmFsc190YXIscHZhbHNfdW50YXIsbWV0aG9kID0gInNwZWFybWFuIikKcmhvcCA9IGNvci50ZXN0KHB2YWxzX3RhcixwdmFsc191bnRhcixtZXRob2QgPSAic3BlYXJtYW4iKSRwLnZhbHVlCnByaW50KAogIGdncGxvdChkZiwgYWVzKHg9dGFyZ2V0ZWQsIHk9dW50YXJnZXRlZCwKICAgICAgICAgICAgICAgICBzaGFwZT1zaWduaWZpY2FudF9kaWZmLCBjb2xvcj1zaWduaWZpY2FudF9pbikpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZ3RpdGxlKHBhc3RlKCItbG9nMTAgcC12YWx1ZXMsIHNwZWFybWFuOiIsZm9ybWF0KHJobyxkaWdpdHM9MiksCiAgICAgICAgICAgICAgICAgICIocD0iLGZvcm1hdChyaG9wLGRpZ2l0cz0zKSwiKSIpKQopCgpwcmludCgiIyMjIFN1bW1hcnkgb2YgZGlmZmVyZW5jZXMgaW4gUkUgbW9kZWxzICMjIyIpCnByaW50KCJNb2RlbCBpcyBzaWduaWZpY2FudCBhdCBwPDAuMDAxOiIpCnByaW50KHRhYmxlKGRmJHNpZ25pZmljYW50X2luKSkKcHJpbnQoIkFkZGluZyBpc190YXJnZXRlZCBhcyBhIGNvdmFyaWF0ZSBoYXMgcDwwLjAwMToiKQpwcmludCh0YWJsZShkZiRzaWduaWZpY2FudF9kaWZmKSkKCiMgQmV0YXMgLSB0YXJnZXRlZCB2cy4gdW50YXJnZXRlZApiZXRhc190YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdGFyJGJldGFbMSwxXSkKYmV0YXNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkYmV0YVsxLDFdKQpiZXRhc191bnRhciA9IHVubGlzdChiZXRhc191bnRhcltzYXBwbHkoYmV0YXNfdW50YXIsbGVuZ3RoKT4wXSkKZGYgPSBkYXRhLmZyYW1lKAogIHRhcmdldGVkID0gYmV0YXNfdGFyLAogIHVudGFyZ2V0ZWQgPSBiZXRhc191bnRhciwKICBzaWduaWZpY2FudF9pbiA9IHNpZ25pZmljYW50X2luLAogIHNpZ25pZmljYW50X2RpZmYgPSBzaWduaWZpY2FudF9kaWZmCikKcmhvID0gY29yKGJldGFzX3VudGFyLGJldGFzX3RhcixtZXRob2QgPSAic3BlYXJtYW4iKQpyaG9wID0gY29yLnRlc3QoYmV0YXNfdW50YXIsYmV0YXNfdGFyLG1ldGhvZCA9ICJzcGVhcm1hbiIpJHAudmFsdWUKcHJpbnQoCiAgZ2dwbG90KGRmLCBhZXMoeD10YXJnZXRlZCwgeT11bnRhcmdldGVkLAogICAgICAgICAgICAgICAgIHNoYXBlPXNpZ25pZmljYW50X2RpZmYsIGNvbG9yPXNpZ25pZmljYW50X2luKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdndGl0bGUocGFzdGUoIkVmZmVjdCBzaXplcywgc3BlYXJtYW46Iixmb3JtYXQocmhvLGRpZ2l0cz0yKSwKICAgICAgICAgICAgICAgICAgIihwPSIsZm9ybWF0KHJob3AsZGlnaXRzPTMpLCIpIikpCikKYGBgCgpGcm9tIHRoZSBwbG90cyBhYm92ZSB3ZSB0YWtlIHRoZSBtb3N0IGV4dHJlbWUgZXhhbXBsZXMgYW5kIGV4YW1pbmUgdGhlaXIgZm9yZXN0IHBsb3RzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQphZ3JlZV9leGFtcGxlID0gbmFtZXMoc2FtcGxlKHdoaWNoKHB2YWxzX3RhcjwgMWUtMTAgJiBwdmFsc191bnRhciA8IDFlLTEwICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldGVkX2RpZmZfcCA+IDAuMSkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfZXhhbXBsZV1dJHJlX21vZGVsMSwKICBzbGFiID0gbWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfZXhhbXBsZV1dW1sxXV1bLDFdLAogIG1haW4gPSBwYXN0ZShhZ3JlZV9leGFtcGxlLCJzaWduaWZpY2FudCBpbiBib3RoLCB0YXIgYW5kIHVudGFyIGFncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmFncmVlX3BfZGlzYWdyZWVfYmV0YSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTEwICYgcHZhbHNfdW50YXIgPCAxZS0xMCAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRlZF9kaWZmX3AgPCAwLjAwMSkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfcF9kaXNhZ3JlZV9iZXRhXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9wX2Rpc2FncmVlX2JldGFdXVtbMV1dWywxXSwKICBtYWluID0gcGFzdGUoYWdyZWVfcF9kaXNhZ3JlZV9iZXRhLAogICAgICAgICAgICAgICAic2lnbmlmaWNhbnQgaW4gYm90aCwgdGFyIGFuZCB1bnRhciBkaXNhZ3JlZSIsc2VwPSJcbiIpLAogIHhsYWIgPSAiTG9nIGZjIixjb2wgPSAiYmx1ZSIpCgpkaXNhZ3JlZV9leGFtcGxlMSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTIwICYgcHZhbHNfdW50YXIgPjAuMSkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbZGlzYWdyZWVfZXhhbXBsZTFdXSRyZV9tb2RlbDEsCiAgc2xhYiA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV1bWzFdXVssMV0sCiAgbWFpbiA9IHBhc3RlKGRpc2FncmVlX2V4YW1wbGUxLAogICAgICAgICAgICAgICAic2lnbmlmaWNhbnQgaW4gYm90aCwgdGFyIGFuZCB1bnRhciBhZ3JlZSIsc2VwPSJcbiIpLAogIHhsYWIgPSAiTG9nIGZjIixjb2wgPSAiYmx1ZSIpCgoKZGlzYWdyZWVfZXhhbXBsZTIgPSBuYW1lcyhzYW1wbGUod2hpY2gocHZhbHNfdGFyID4gMC4xICYgcHZhbHNfdW50YXIgPCAxZS0yMCkpWzFdKQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbZGlzYWdyZWVfZXhhbXBsZTJdXSRyZV9tb2RlbDEsCiAgc2xhYiA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUyXV1bWzFdXVssMV0sCiAgbWFpbiA9IHBhc3RlKGRpc2FncmVlX2V4YW1wbGUyLAogICAgICAgICAgICAgICAic2lnbmlmaWNhbnQgaW4gYm90aCwgdGFyIGFuZCB1bnRhciBhZ3JlZSIsc2VwPSJcbiIpLAogIHhsYWIgPSAiTG9nIGZjIixjb2wgPSAiYmx1ZSIpCgpgYGAKCgojIFRhcmdldGVkIHZzLiB1bnRhcmdldGVkOiBjb21wYXJpc29uIGFzIGEgcHJlZGljdGlvbiB0YXNrCgpVc2UgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIGZvciBhbmFseXNpcyB3aXRoaW4gdGlzc3Vlcy4KCmBgYHtyLGV2YWw9RkFMU0UsZmlnLmFsaWduPSJjZW50ZXIiLG91dC5oZWlnaHQ9JzYwJScsb3V0LndpZHRoPSc2MCUnLGV2YWw9RkFMU0V9Cm5mb2xkcyA9IDUKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgZm9yKG5uMiBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgaWYoIWdyZXBsKCJ1bnRhcmdldGVkIixubjIpKXtuZXh0fQogICAgbm4yX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMV0KICAgIG5uMl90aXNzdWUgPSBnc3ViKCJfcG93ZGVyIiwiIixubjJfdGlzc3VlKQogICAgbm4yX2RhdGFzZXQgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzJdCiAgICBpZihubjFfdGlzc3VlIT1ubjJfdGlzc3VlKXtuZXh0fQogICAgcHJpbnQocGFzdGUoImZlYXR1cmVzIGZyb206IixubjIpKQogICAgcHJpbnQocGFzdGUoImxhYmVscyBmcm9tOiIsbm4xKSkKICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX2RhdGEKICAgIHggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRzYW1wbGVfZGF0YQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IHQoYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkpCiAgICB5ID0gdChhcy5tYXRyaXgoeVssc2hhcmVkX2JpZHNdKSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgdGFrZSB0aGUgY292YXJpYXRlcyAoaWdub3JlIGRpc3RhbmNlcykKICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKHlfbWV0YVssY3Vycl9jb3ZfY29sc10pCiAgICBuYW1lcyhjdXJyX2NvdnMpID0gY3Vycl9jb3ZfY29scwogICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgICMgYWRkIHRoZSBjb3ZhcmlhdGVzIGludG8geAogICAgeCA9IGNiaW5kKHgsY3Vycl9jb3ZzKQogICAgCiAgICAjIFJ1biB0aGUgcmVncmVzc2lvbnMKICAgIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgICBwcmVkcyA9IGMoKTtyZWFsPWMoKQogICAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgICAgaWYoIGkgJSUgMTAgPT0gMCl7cHJpbnQocGFzdGUoImFuYWx5emluZyBtZXRhYm9saXRlIG51bWJlcjoiLGkpKX0KICAgICAgeV9pID0geVssMV0KICAgICAgaV9wcmVkcyA9IGMoKTtpX3JlYWw9YygpCiAgICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgICAgdHJfeWkgPSB5X2lbZm9sZHMhPWpdCiAgICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICAgIyByYW5kb20gZm9yZXN0CiAgICAgICAgIyBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgICAjIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgICBtb2RlbCA9IGZlYXR1cmVfc2VsZWN0aW9uX3dyYXBwZXIodHJfeCx0cl95aSwKICAgICAgICAgICAgICAgICAgIGNvZWZmX29mX3ZhcixyYW5kb21Gb3Jlc3QsCiAgICAgICAgICAgICAgICAgICB0b3BLID0gbnVtRmVhdHVyZXMsbnRyZWU9NTApCiAgICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICAgIGlfcHJlZHMgPSBjKGlfcHJlZHMsdGVfcHJlZHMpCiAgICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgICAgfQogICAgICBwcmVkcyA9IGNiaW5kKHByZWRzLGlfcHJlZHMpCiAgICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICAgIH0KICAgIGN1cnJuYW1lID0gcGFzdGUobm4xLG5uMixzZXA9IjsiKQogICAgcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tjdXJybmFtZV1dID0gbGlzdCgKICAgICAgcHJlZHMgPSBwcmVkcyxyZWFsPXJlYWwKICAgICkKICB9Cn0KbmFtZXMocHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzKQoKY292X3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBwcmludChubjEpCiAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9kYXRhCiAgeV92aWFscyA9IGNvbG5hbWVzKHkpCiAgYmlkX3kgPSBtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh5KSwiYmlkIl0KICBjb2xuYW1lcyh5KSA9IGJpZF95CiAgeSA9IHQoYXMubWF0cml4KHkpKQogIGlmKG5jb2woeSk+MTAwMCl7bmV4dH0KICBjb3ZfY29scyA9IGMoImFuaW1hbC5yZWdpc3RyYXRpb24uc2V4IiwKICAgICAgICAgICAgICJhY3V0ZS50ZXN0LndlaWdodCIsCiAgICAgICAgICAgICAiYWN1dGUudGVzdC5kaXN0YW5jZSIsCiAgICAgICAgICAgICAiYW5pbWFsLmtleS50aW1lcG9pbnQiKQogIGNvdnMgPSBtZXJnZWRfZG1hcWNfZGF0YVt5X3ZpYWxzLGNvdl9jb2xzXQogIHggPSBjb3ZzCiAgCiAgIyBSdW4gdGhlIHJlZ3Jlc3Npb25zCiAgZm9sZHMgPSBzYW1wbGUocmVwKDE6bmZvbGRzLCgxK25yb3coeCkvbmZvbGRzKSkpWzE6bnJvdyh4KV0KICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgcHJlZHMgPSBjKCk7cmVhbD1jKCkKICBmb3IoaSBpbiAxOm5jb2woeSkpewogICAgeV9pID0geVssMV0KICAgIGlfcHJlZHMgPSBjKCk7aV9yZWFsPWMoKQogICAgZm9yKGogaW4gMTpuZm9sZHMpewogICAgICBwcmludChqKQogICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgIHRyX3lpID0geV9pW2ZvbGRzIT1qXQogICAgICB0ZV94ID0geFtmb2xkcz09aixdCiAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICMgcmFuZG9tIGZvcmVzdAogICAgICBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICBpX3ByZWRzID0gYyhpX3ByZWRzLHRlX3ByZWRzKQogICAgICBpX3JlYWwgPSBjKGlfcmVhbCx0ZV95KQogICAgfQogICAgcHJlZHMgPSBjYmluZChwcmVkcyxpX3ByZWRzKQogICAgcmVhbCA9IGNiaW5kKHJlYWwsaV9yZWFsKQogIH0KICBjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubjFdXSA9IGxpc3QoCiAgICAgIHByZWRzID0gcHJlZHMscmVhbD1yZWFsCiAgICApCn0KCnJlc3VsdHNfbWV0cmljcyA9IGMoKQpmb3Iobm4gaW4gbmFtZXMocHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzKSl7CiAgcHJlZHMgPSBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW25uXV0kcHJlZHMKICByZWFsID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHJlYWwKICByaG9zMSA9IGRpYWcoY29yKHByZWRzLHJlYWwpKQogIHJob3MyID0gZGlhZyhjb3IocHJlZHMscmVhbCxtZXRob2Q9InNwZWFybWFuIikpCiAgU0VzID0gKHByZWRzLXJlYWwpXjIKICBtc2UgPSBtZWFuKFNFcykKICBub3JtU0VzID0gU0VzIC8gYXBwbHkocmVhbCwyLHNkKQogIGN1cnJfc2NvcmVzID0gYyhtZWFuKHJob3MxKSxtZWFuKHJob3MyKSxtaW4ocmhvczEpLG1pbihyaG9zMiksCiAgICAgICAgICAgICAgICAgIG1zZSxtZWFuKG5vcm1TRXMpKQogIG5hbWVzKGN1cnJfc2NvcmVzKSA9IGMoIm1lYW5fcmhvIiwibWVhbl9zcGVhcm1hbl9yaG8iLCJtaW5fcmhvIiwibWluX3NwZWFybWFuX3JobyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiTVNFIiwibWVhbiBNU0UvU0QiKQogIHJlc3VsdHNfbWV0cmljcyA9IHJiaW5kKHJlc3VsdHNfbWV0cmljcywKICAgICAgICAgICAgICAgICAgICAgICAgICBjdXJyX3Njb3JlcykKICByb3duYW1lcyhyZXN1bHRzX21ldHJpY3MpW25yb3cocmVzdWx0c19tZXRyaWNzKV0gPSBubgp9Cgpjb3ZfcmVzdWx0c19tZXRyaWNzID0gYygpCmZvcihubiBpbiBuYW1lcyhjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzKSl7CiAgcHJlZHMgPSBjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHByZWRzCiAgcmVhbCA9IGNvdl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW25uXV0kcmVhbAogIHJob3MxID0gZGlhZyhjb3IocHJlZHMscmVhbCkpCiAgcmhvczIgPSBkaWFnKGNvcihwcmVkcyxyZWFsLG1ldGhvZD0ic3BlYXJtYW4iKSkKICBTRXMgPSAocHJlZHMtcmVhbCleMgogIG1zZSA9IG1lYW4oU0VzKQogIG5vcm1TRXMgPSBTRXMgLyBhcHBseShyZWFsLDIsc2QpCiAgY3Vycl9zY29yZXMgPSBjKG1lYW4ocmhvczEpLG1lYW4ocmhvczIpLG1pbihyaG9zMSksbWluKHJob3MyKSwKICAgICAgICAgICAgICAgICAgbXNlLG1lYW4obm9ybVNFcykpCiAgbmFtZXMoY3Vycl9zY29yZXMpID0gYygibWVhbl9yaG8iLCJtZWFuX3NwZWFybWFuX3JobyIsIm1pbl9yaG8iLCJtaW5fc3BlYXJtYW5fcmhvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJNU0UiLCJtZWFuIE1TRS9TRCIpCiAgY292X3Jlc3VsdHNfbWV0cmljcyA9IHJiaW5kKGNvdl9yZXN1bHRzX21ldHJpY3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY3Vycl9zY29yZXMpCiAgcm93bmFtZXMoY292X3Jlc3VsdHNfbWV0cmljcylbbnJvdyhjb3ZfcmVzdWx0c19tZXRyaWNzKV0gPSBubgp9CgojIFNvbWUgYm94cGxvdHMKcHJlZF90YXJnZXRzID0gc2FwcGx5KG5hbWVzKHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cyksZnVuY3Rpb24oeCkKICBzdHJzcGxpdCh4LHNwbGl0ID0gIjsiKVtbMV1dWzFdKQp0YXJnZXQgPSAibGl2ZXJfcG93ZGVyLG1ldGFiX3RfdGNhLG5hbWVkIiAKY3Vycl9yZXMgPSB1bmlxdWUocmVzdWx0c19tZXRyaWNzW3ByZWRfdGFyZ2V0cyA9PSB0YXJnZXQsXSkKcm93bmFtZXMoY3Vycl9yZXMpID0gZ3N1Yih0YXJnZXQsIiIscm93bmFtZXMoY3Vycl9yZXMpKQpyb3duYW1lcyhjdXJyX3JlcykgPSBnc3ViKCI7IiwiIixyb3duYW1lcyhjdXJyX3JlcykpCnJvd25hbWVzKGN1cnJfcmVzKVtyb3duYW1lcyhjdXJyX3Jlcyk9PSIiXSA9IHRhcmdldApjb3ZfYmFzZWxpbmUgPSBjb3ZfcmVzdWx0c19tZXRyaWNzW3RhcmdldCxdCgpwYXIobWFyID0gYyg4LDQsMiwyKSkKY29scyA9IHJlcCgiYmx1ZSIsbnJvdyhjdXJyX3JlcykpCmNvbHNbcm93bmFtZXMoY3Vycl9yZXMpPT10YXJnZXRdID0gImJsYWNrIgpwbHQgPSBiYXJwbG90KGN1cnJfcmVzWyw0XSxiZXNpZGUgPSBULHhheHQ9Im4iLGxlZ2VuZD1GLAogICAgICAgICAgICAgIHlsYWI9Ik1pbiByaG8gKFNwZWFybWFuKSIsCiAgICAgICAgICAgICAgeWxpbSA9IGMoMC41LDEpLHhwZD1GLGNvbD1jb2xzLAogICAgICAgICAgICAgIG1haW4gPSB0YXJnZXQpCnRleHQocGx0LCBwYXIoInVzciIpWzNdLCBsYWJlbHMgPSByb3duYW1lcyhjdXJyX3JlcyksIAogICAgIHNydCA9IDQ1LCBhZGogPSBjKDEuMSwxLjEpLCB4cGQgPSBULCBjZXg9MC42KQphYmxpbmUoaD1jb3ZfYmFzZWxpbmVbNF0sbHR5PTIsbHdkPTIsY29sPSJyZWQiKQoKcGFyKG1hciA9IGMoOCw0LDIsMikpCnBsdCA9IGJhcnBsb3QoY3Vycl9yZXNbLDZdLGJlc2lkZSA9IFQseGF4dD0ibiIsbGVnZW5kPUYsCiAgICAgICAgICAgICAgeWxhYj0iTWVhbiBzdGFuZGFyZGl6ZWQgU0UiLHhwZD1GLAogICAgICAgICAgICAgIG1haW4gPSB0YXJnZXQsY29sPWNvbHMpCnRleHQocGx0LCBwYXIoInVzciIpWzNdLCBsYWJlbHMgPSByb3duYW1lcyhjdXJyX3JlcyksIAogICAgIHNydCA9IDQ1LCBhZGogPSBjKDEuMSwxLjEpLCB4cGQgPSBULCBjZXg9MC42KQphYmxpbmUoaD1jb3ZfYmFzZWxpbmVbNl0sbHR5PTIsbHdkPTIsY29sPSJyZWQiKQoKIyBwcmVkcyA9IGMoKTtyZWFsPWMoKQojIGZvcihqIGluIDE6bmZvbGRzKXsKIyAgIHRyX3ggPSB4W2ZvbGRzIT1qLF0KIyAgIHRyX3kgPSB5W2ZvbGRzIT1qLF0KIyAgIHRlX3ggPSB4W2ZvbGRzPT1qLF0KIyAgIHRlX3kgPSB5W2ZvbGRzPT1qLF0KIyAgIG1vZGVsID0gTVRMX3dyYXBwZXIodHJfeCx0cl95LHR5cGU9IlJlZ3Jlc3Npb24iLCBSZWd1bGFyaXphdGlvbj0iTDIxIikKIyAgIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCx0ZV94KQojICAgcmVhbCA9IHJiaW5kKHJlYWwsdGVfeSkKIyAgIHByZWRzID0gcmJpbmQocHJlZHMsdGVfcHJlZHMpCiMgfQojIGRpYWcoY29yKHByZWRzLHJlYWwpKQoKIyBVc2luZyBQTFMgcmVncmVzc2lvbgojIGxpYnJhcnkocGxzKQojIHBsc19tb2RlbCA9IHBsc3IoeX54LG5jb21wID0gNSx2YWxpZGF0aW9uPSJMT08iKQojIGV2YWwgPSBNU0VQKHBsc19tb2RlbCkKIyAKIyB5X3BjYSA9IHByY29tcCh5KQojIHBsb3QoeV9wY2EpCiMgZXhwbGFpbmVkX3ZhciA9IHlfcGNhJHNkZXZeMi9zdW0oeV9wY2Ekc2Rldl4yKQojIHlfcGNhX21hdHJpeCA9IHlfcGNhJHhbLDE6MTBdCiMgCiMgIyByZWdyZXNzIG91dCBzZXgsIHdlaWdodAojIAojIGdldF9leHBsYWluZWRfdmFyaWFuY2VfdXNpbmdfUENBKHgseSkKIyB4ID0gYXBwbHkoeCwyLHJlZ3Jlc3Nfb3V0LGNvdnM9Y292cykKIyB5ID0gYXBwbHkoeSwyLHJlZ3Jlc3Nfb3V0LGNvdnM9Y292cykKIyBnZXRfZXhwbGFpbmVkX3ZhcmlhbmNlX3VzaW5nX1BDQSh4LHkpCgoKYGBgCgo8IS0tICMjIENvbXBhcmlzb24gb2YgY292YXJpYW5jZSBtYXRyaWNlcyAtLT4KCjwhLS0gYGBge3J9IC0tPgoKPCEtLSBDViA8LWZ1bmN0aW9uKHgpeyAtLT4KPCEtLSAgIHJldHVybihzZCh4KS9tZWFuKHgpKSAtLT4KPCEtLSB9IC0tPgoKPCEtLSAjIEdldCBhbGwgY29ycmVsYXRpb24gYW5kIGNvdmFyaWFuY2UgbWF0cmljZXMgLS0+CjwhLS0geSA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbWzFdXSRzYW1wbGVfZGF0YSAjIGFuY2hvciBhbGwgZGF0YXNldHMgYnkgdGhlc2UgaWRzIC0tPgo8IS0tIHlfdmlhbHMgPSBjb2xuYW1lcyh5KSAtLT4KPCEtLSBiaWRfeSA9IGFzLmNoYXJhY3RlcihtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh5KSwiYmlkIl0pIC0tPgo8IS0tIGNvdl9jb2xzID0gYygiYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXgiLCAtLT4KPCEtLSAgICAgICAgICAgICAgImFjdXRlLnRlc3Qud2VpZ2h0IiwgLS0+CjwhLS0gICAgICAgICAgICAgICJhY3V0ZS50ZXN0LmRpc3RhbmNlIikgLS0+CjwhLS0gY292cyA9IG1lcmdlZF9kbWFxY19kYXRhW3lfdmlhbHMsY292X2NvbHNdIC0tPgoKPCEtLSBjb3ZfbWF0cmljZXMgPSBsaXN0KCkgLS0+CjwhLS0gY29yX21hdHJpY2VzID0gbGlzdCgpIC0tPgo8IS0tIGZvcihubiBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzKSl7IC0tPgo8IS0tICAgeCA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbW25uXV0kc2FtcGxlX2RhdGEgLS0+CjwhLS0gICBiaWRfeCA9IGFzLmNoYXJhY3RlcihtZXJnZWRfZG1hcWNfZGF0YVtjb2xuYW1lcyh4KSwiYmlkIl0pIC0tPgo8IS0tICAgcHJpbnQoc3VtKCFpcy5lbGVtZW50KGJpZF95LHNldD1iaWRfeCkpKSAjIHNob3VsZCBiZSB6ZXJvIC0tPgo8IS0tICAgY29sbmFtZXMoeCkgPSBiaWRfeCAtLT4KPCEtLSAgIHggPSB4WyxiaWRfeV0gLS0+CjwhLS0gICAjIHggPSBhcHBseSh4LDEscmVncmVzc19vdXQsY292cz1jb3ZzKSAtLT4KPCEtLSAgICMgeCA9IHQoeCkgLS0+CjwhLS0gICAjIGN2cyA9IGFwcGx5KHgsMSxDVikgLS0+CjwhLS0gICAjIHByaW50KHRhYmxlKGN2cz4wLjUpKSAtLT4KPCEtLSAgICMgeCA9IHhbY3ZzPjAuNSxdIC0tPgo8IS0tICAgIyBwY2F4ID0gdChwcmNvbXAodCh4KSxzY2FsZS4gPSBGKSR4WywxOjEwXSkgLS0+CjwhLS0gICBjb3ZfbWF0cmljZXNbW25uXV0gPSBjb3YoeCkgLS0+CjwhLS0gICBjb3JfbWF0cmljZXNbW25uXV0gPSBjb3IoeCkgLS0+CjwhLS0gfSAtLT4KPCEtLSBzYXBwbHkoY29yX21hdHJpY2VzLGRpbSkgLS0+CjwhLS0gbGlicmFyeSgnZXZvbHFnJyk7bGlicmFyeShjb3JycGxvdCkgLS0+CjwhLS0gbWFudGVsX3Rlc3RzPSBNYW50ZWxDb3IoY29yX21hdHJpY2VzKSAtLT4KPCEtLSBtY29ycz0gTWF0cml4Q29yKGNvcl9tYXRyaWNlcykgLS0+CjwhLS0gbWNvcnMgPSBhcy5tYXRyaXgoZm9yY2VTeW1tZXRyaWMobWNvcnMsdXBsbz0iTCIpKSAtLT4KPCEtLSBtYW50ZWxfdGVzdHMgPSBhcy5tYXRyaXgoIC0tPgo8IS0tICAgZm9yY2VTeW1tZXRyaWMobWFudGVsX3Rlc3RzJHByb2JhYmlsaXRpZXMsdXBsbyA9ICJMIikpIC0tPgo8IS0tICMgZGlhZyhtYW50ZWxfdGVzdHMpPTAgLS0+CjwhLS0gb3JkID0gY29ycnBsb3QodChtY29ycyksb3JkZXI9ImhjbHVzdCIpIC0tPgo8IS0tIG9yZCA9IHJvd25hbWVzKG9yZCkgLS0+CjwhLS0gY29ycnBsb3QobWNvcnNbb3JkLG9yZF0scC5tYXQ9bWFudGVsX3Rlc3RzW29yZCxvcmRdLCAtLT4KPCEtLSAgICAgICAgICBpbnNpZyA9ICJsYWJlbF9zaWciLG1ldGhvZD0ic2hhZGUiLCAtLT4KPCEtLSAgICAgICAgICBzaWcubGV2ZWwgPSBjKC4wMDAxLCAuMDAxLCAuMDEpLCAtLT4KPCEtLSAgICAgICAgICBwY2guY2V4ID0gLjksIHBjaC5jb2wgPSAiYmxhY2siKSAtLT4KPCEtLSAjIHJzX3JlcyA9IFJhbmRvbVNrZXdlcnMoY29yX21hdHJpY2VzKSAtLT4KPCEtLSAjIGNvcnJwbG90KHJzX3JlcyRjb3JyZWxhdGlvbnMsIC0tPgo8IS0tICMgICAgICAgICAgcC5tYXQ9bWFudGVsX3Rlc3RzJHByb2JhYmlsaXRpZXMsdHlwZT0ibG93ZXIiLCAtLT4KPCEtLSAjICAgICAgICAgIGluc2lnID0gImxhYmVsX3NpZyIsbWV0aG9kPSJzaGFkZSIsIC0tPgo8IS0tICMgICAgICAgICAgc2lnLmxldmVsID0gYyguMDAwMSwgLjAwMSwgLjAxKSwgLS0+CjwhLS0gIyAgICAgICAgICBwY2guY2V4ID0gLjksIHBjaC5jb2wgPSAiYmxhY2siLHRsLmNleD0wLjYpIC0tPgoKPCEtLSBsaWJyYXJ5KHBzeWNoKSAtLT4KCjwhLS0gcjEgPSBjb3JfbWF0cmljZXNbWzFdXSAtLT4KPCEtLSByMiA9IGNvcl9tYXRyaWNlc1tbOF1dIC0tPgo8IS0tIGNvcihyMVtsb3dlci50cmkocjEpXSxyMltsb3dlci50cmkocjIpXSkgLS0+CjwhLS0gbWFudGVsX3Rlc3RzWzEsOF0gLS0+CjwhLS0gbWFudGVsX3Rlc3RzWzgsMV0gLS0+Cgo8IS0tIHRocmVzaG9sZF9jb21wID0gYXBwbHlfZnVuY3Rpb25fb25fcGFpcnMoY29yX21hdHJpY2VzLCAtLT4KPCEtLSAgICAgICAgICAgZnVuY3Rpb24oeCx5KXN1bSh4PjAuNyZ5PjAuNykpIC0tPgo8IS0tIGNvcnJwbG90KHRocmVzaG9sZF9jb21wLGlzLmNvcnIgPSBGLG9yZGVyPSJoY2x1c3QiKSAtLT4KCjwhLS0gdGhyZXNob2xkX2NvbXAgPSBhcHBseV9mdW5jdGlvbl9vbl9wYWlycyhjb3JfbWF0cmljZXMsIC0tPgo8IS0tICAgICAgICAgIGZ1bmN0aW9uKHgseSltZWFuKGRpYWcoY29yKHgseSkpKSkgLS0+CjwhLS0gY29ycnBsb3QodGhyZXNob2xkX2NvbXAsaXMuY29yciA9IEYsb3JkZXI9ImhjbHVzdCIpIC0tPgoKPCEtLSBgYGAgLS0+CgoKCgoKCgoKCgoKCgoK